Tarjan算法专练
Posted codancer
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Tarjan算法专练相关的知识,希望对你有一定的参考价值。
1.迷宫城堡
题意:给一个图判断是否是强连通图。
题解:利用Tarjan计算图中强连通分量的个数,如果为1则是强连通图,否则不是。
#include<bits/stdc++.h>
using namespace std;
const int N = 2e4+100;
typedef long long ll;
vector<int> G[N];
bool is_instack[N];
int dfn[N],low[N];
stack<int> sta;
int n,m,index,scc;
void init(){
index=scc=0;
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
memset(is_instack,0,sizeof(is_instack));
while(!sta.empty()) sta.pop();
for(int i=1;i<=n;i++) G[i].clear();
}
void Tarjan(int u){
dfn[u]=low[u]=++index;
sta.push(u);is_instack[u]=1;
for(auto v:G[u]){
if(!dfn[v]){
Tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(is_instack[v]){
low[u]=min(low[u],dfn[v]);
}
}
if(low[u]==dfn[u]){
++scc;
while(sta.top()!=u){
is_instack[sta.top()]=0;
sta.pop();
}
}
}
int main(){
while(scanf("%d %d",&n,&m)){
if(n+m==0) break;
init();
int u,v;
for(int i=0;i<m;i++){
scanf("%d %d",&u,&v);
G[u].push_back(v);
}
for(int i=1;i<=n;i++){
if(!dfn[i]) Tarjan(i);
}
if(scc==1) puts("Yes");
else puts("No");
}
return 0;
}
2.Proving Equivalences
题意:给定一个有向图,求最少加几条有向边使得整个图成为强连通。
题解:Tarjan缩点之后计算入读为(0)的个数(a)和出度为(0)的个数(b),取最大值(max(a,b)),注意如果已经是强连通图了,则答案为(0).
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+100;
vector<int> G[N],color[N];
stack<int> sta;
int dfn[N],low[N];
int id[N],od[N];
bool is_instack[N];
int scc[N],nscc,index;
int n,m;
void init(){
nscc=index=0;
memset(scc,0,sizeof(scc));
memset(dfn,0,sizeof(dfn));
memset(id,0,sizeof(id));
memset(od,0,sizeof(od));
memset(is_instack,0,sizeof(is_instack));
memset(low,0,sizeof(low));
for(int i=1;i<=n;i++) G[i].clear(),color[i].clear();
while(!sta.empty()) sta.pop();
}
void Tarjan(int u){
low[u]=dfn[u]=++index;
sta.push(u);is_instack[u]=1;
for(auto v:G[u]){
if(!dfn[v]){
Tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(is_instack[v]){
low[u]=min(low[u],dfn[v]);
}
}
if(low[u]==dfn[u]){
++nscc;
while(1){
int temp=sta.top();
scc[temp]=nscc;
sta.pop();
is_instack[temp]=0;
if(temp==u) break;
}
}
}
int main(){
int T;
scanf("%d",&T);
while(T--){
scanf("%d %d",&n,&m);
init();
int u,v;
for(int i=1;i<=m;i++){
scanf("%d %d",&u,&v);
G[u].push_back(v);
}
for(int i=1;i<=n;i++){
if(!dfn[i]) Tarjan(i);
}
//cerr<<nscc<<endl;
if(nscc==1){
puts("0");
}
else{
for(int i=1;i<=nscc;i++) id[i]=od[i]=1;
for(int i=1;i<=n;i++){
for(auto j:G[i]){
if(scc[i]!=scc[j]){
id[scc[j]]=od[scc[i]]=0;
}
}
}
int n1=0;int n2=0;
for(int i=1;i<=nscc;i++){
n1+=id[i];n2+=od[i];
}
printf("%d
",max(n1,n2));
}
}
return 0;
}
3.Summer Holiday
题意:给你一个有向图,每个点都有一个权值,你需要在这个图中选择几个点使得利用这几个点可以遍历完整个图,同时还要满足选择的这几个点的权值和最小。
题解:Tarjan缩点,然后在缩点的时候把点的权值缩成所在的强连通分量里面权值最小的,然后寻找入度为0的点的个数,即为答案。
{% fold 点击显/隐内容 %}
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+100;
typedef long long ll;
const int INF = 0x3f3f3f3f;
int n,m,scc,index;
vector<int> G[N];
ll w[N],low[N],dfn[N],minn[N],color[N],id[N];
bool is_instack[N];stack<int> sta;
void init(){
scc=index=0;
memset(low,0,sizeof(low));
memset(dfn,0,sizeof(dfn));
memset(color,0,sizeof(color));
memset(minn,INF,sizeof(minn));
memset(is_instack,0,sizeof(is_instack));
for(int i=1;i<=n;i++) G[i].clear();
while(!sta.empty()) sta.pop();
}
void Tarjan(int u){
low[u]=dfn[u]=++index;
sta.push(u);is_instack[u]=1;
for(auto v:G[u]){
if(!dfn[v]){
Tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(is_instack[v]){
low[u]=min(low[u],dfn[v]);
}
}
if(low[u]==dfn[u]){
++scc;
while(1){
int temp=sta.top();
color[temp]=scc;
minn[scc]=min(minn[scc],w[temp]);
is_instack[temp]=0;
sta.pop();
if(temp==u) break;
}
}
}
int main(){
while(scanf("%d %d",&n,&m)!=EOF){
init();
for(int i=1;i<=n;i++) scanf("%lld",&w[i]);
int u,v;
for(int i=1;i<=m;i++){
scanf("%d %d",&u,&v);
G[u].push_back(v);
}
for(int i=1;i<=n;i++){
if(!dfn[i]) Tarjan(i);
}
for(int i=1;i<=scc;i++) id[i]=1;
for(int i=1;i<=n;i++){
for(auto j:G[i]){
if(color[i]!=color[j]){
id[color[j]]=0;
}
}
}
ll ans1,ans2;ans1=ans2=0;
for(int i=1;i<=scc;i++){
if(id[i]){
ans1++;
ans2+=minn[i];
}
}
printf("%lld %lld
",ans1,ans2);
}
return 0;
}
{% endfold %}
4.Intelligence System
题意:一个人际关系网,0号可以联系上任意一个人,如果两个人可以直接或间接的互相联系,那么这两个人的消费为0,求0号要联系上每个人最小的消费。
题解: Tarjan缩点,求最小树形图,由于题目保证有解,因此只需要统计每个点入边权值最小的即可。0所在的强连通分量不用考虑。
#include<bits/stdc++.h>
using namespace std;
const int N = 50005 ;
typedef long long ll;
const ll INF = 0x3f3f3f3f;
ll low[N],dfn[N],minn[N],color[N];
bool is_instack[N];
ll n,m,scc,index;
stack<ll> sta;
vector<pair<ll,ll> > G[N];
void init(){
scc=index=0;
for(int i=0;i<=n;i++){
is_instack[i]=0;
low[i]=0;
dfn[i]=0;
minn[i]=INF;
G[i].clear();
}
while(!sta.empty()) sta.pop();
}
void Tarjan(ll u){
low[u]=dfn[u]=++index;
is_instack[u]=1;sta.push(u);
for(auto V:G[u]){
ll v=V.first;
if(!dfn[v]){
Tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(is_instack[v]){
low[u]=min(low[u],dfn[v]);
}
}
if(low[u]==dfn[u]){
scc++;
while(1){
ll temp=sta.top();
color[temp]=scc;
is_instack[temp]=0;
sta.pop();
if(temp==u) break;
}
}
}
int main(){
while(scanf("%lld %lld",&n,&m)!=EOF){
map<pair<ll,ll>,ll> WW;
//pair<ll,ll> pnow;
init();
ll u,v,w;
for(ll i=1;i<=m;i++){
scanf("%lld %lld %lld",&u,&v,&w);
//pnow.first=u;pnow.second=v;
//if(!WW[pnow]) WW[pnow]=w;
//else WW[pnow]=min(WW[pnow],w);
G[u].push_back(make_pair(v,w));
}
long long ans=0;
for(ll i=0;i<n;i++){
if(!dfn[i]) Tarjan(i);
}
for(ll i=0;i<n;i++){
for(auto j:G[i]){
ll x=color[i];
ll y=color[j.first];
if(x!=y) minn[y]=min(minn[y],(ll)j.second);
}
}
for(ll i=1;i<=scc;i++){
if(i!=color[0]) ans+=minn[i];
}
printf("%lld
",ans);
}
return 0;
}
以上是关于Tarjan算法专练的主要内容,如果未能解决你的问题,请参考以下文章