转载请注明原blog地址(
http://blog.csdn.net/stl112514)
学习了一下tarjan在acm里面的应用,首先仰慕下Robert Tarjan菊苣,摸完之后呢,写个总结。
据我所知,主要是用于以下三点:1、有向图中求极大强连通分量(无向图就拆成有向图处理);2、无向图中求桥(也就是割边,当然,顺便求下割点);3、无向图中求极大双连通分量(好像有一种说法说分为点连通分量和边连通分量,我感觉都是一个东西)。
据我所知,tarjan能够在O(n)的时间复杂度内求出上面提到的三个东西,竟然只是依托了两个辅助数组,实在是很神奇,不愧是上古菊苣啊。也就是,dfn:记录点被访问的时间戳;low:记录点直接或者间接连通的点的最小的dfn(在有向图中要用一个额外的栈把点区分开来,至于为什么,等下讨论),当然,这个是越小越好,我才疏学浅并不知道tarjan是怎么想出这种办法的,但是经过实践是非常有效的,理论上的证明我并不会。
接下来,我们来讨论怎么使用这两个数组,
1、有向图中求极大强连通分量:
把所有搜索树中的点都丢到栈里,把dfn的值赋值给low,再用直接连通的点更新某点的low,分两种情况,假设当前访问点u,且v是u的一个直接连通点,1、若v没被访问,则先访问,然后low[u]=min(low[u],low[v]);2、若v在栈里,则low[u]=min(low[u],dfn[v]),这是出现环时的特判,至于为啥这样,并不知道;其实还有情况3、v被访问,且不在栈里,则无视,因为这说明v在另一个极大强连通分量里,u不该去打扰。最后当出现low[u]==dfn[u]时,则说明以u为根的搜索树上的点构成了一个极大强连通分量,为啥?因为找不到比dfn[u]更小的dfn了,是不是刚好走完了一个极大强连通分量?这是十分巧妙的。
2、无向图中求桥:
当low[v]>dfn[u]时,边(u,v)就是桥,因为v无论如何也找不到不大于dfn[u]的dfn时,则说明v在另一个极大双连通分量内。为了让上述结论有用,我们规定一旦走了(u,v)就不再走回去,当然若有重边的情况就这样说,一条边只能走一次。
3、无向图中求极大双连通分量:
在求桥的基础上,当出现low[u]==dfn[u]时,以u为根的搜索树上的点构成了一个极大双连通分量,也就是综合1跟2的结论。
就这样,大概说了说理论知识,下面分别是三个题对应上述三种情况,结合下代码很快就能掌握了
hdu1827
http://acm.hdu.edu.cn/showproblem.php?pid=1827
http://acm.hdu.edu.cn/showproblem.php?pid=4738
题意:所有边都有个权值,求权值最小的桥。
特判:如果给出不止一个连通图,输出0;如果最小的桥为0,输出1。
hdu4612
http://acm.hdu.edu.cn/showproblem.php?pid=4612
题意:随意加一条边,让图的桥的数量最小。
做法:双连通分量缩点,重新建图,形成一棵树,求出树的直径,双连通分量的数量减去直径上的点就是答案。要开下栈,用C++交。
学习了一下tarjan在acm里面的应用,首先仰慕下Robert Tarjan菊苣,摸完之后呢,写个总结。
据我所知,主要是用于以下三点:1、有向图中求极大强连通分量(无向图就拆成有向图处理);2、无向图中求桥(也就是割边,当然,顺便求下割点);3、无向图中求极大双连通分量(好像有一种说法说分为点连通分量和边连通分量,我感觉都是一个东西)。
据我所知,tarjan能够在O(n)的时间复杂度内求出上面提到的三个东西,竟然只是依托了两个辅助数组,实在是很神奇,不愧是上古菊苣啊。也就是,dfn:记录点被访问的时间戳;low:记录点直接或者间接连通的点的最小的dfn(在有向图中要用一个额外的栈把点区分开来,至于为什么,等下讨论),当然,这个是越小越好,我才疏学浅并不知道tarjan是怎么想出这种办法的,但是经过实践是非常有效的,理论上的证明我并不会。
接下来,我们来讨论怎么使用这两个数组,
1、有向图中求极大强连通分量:
把所有搜索树中的点都丢到栈里,把dfn的值赋值给low,再用直接连通的点更新某点的low,分两种情况,假设当前访问点u,且v是u的一个直接连通点,1、若v没被访问,则先访问,然后low[u]=min(low[u],low[v]);2、若v在栈里,则low[u]=min(low[u],dfn[v]),这是出现环时的特判,至于为啥这样,并不知道;其实还有情况3、v被访问,且不在栈里,则无视,因为这说明v在另一个极大强连通分量里,u不该去打扰。最后当出现low[u]==dfn[u]时,则说明以u为根的搜索树上的点构成了一个极大强连通分量,为啥?因为找不到比dfn[u]更小的dfn了,是不是刚好走完了一个极大强连通分量?这是十分巧妙的。
2、无向图中求桥:
当low[v]>dfn[u]时,边(u,v)就是桥,因为v无论如何也找不到不大于dfn[u]的dfn时,则说明v在另一个极大双连通分量内。为了让上述结论有用,我们规定一旦走了(u,v)就不再走回去,当然若有重边的情况就这样说,一条边只能走一次。
3、无向图中求极大双连通分量:
在求桥的基础上,当出现low[u]==dfn[u]时,以u为根的搜索树上的点构成了一个极大双连通分量,也就是综合1跟2的结论。
就这样,大概说了说理论知识,下面分别是三个题对应上述三种情况,结合下代码很快就能掌握了
hdu1827
http://acm.hdu.edu.cn/showproblem.php?pid=1827
中文题,做法是tarjan求出极大强连通分量然后缩点,再从重新建图,然后考虑度为零的点是一定要打电话的,就枚举巴拉巴拉什么的吧。
#include<map>
#include<string>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<queue>
#include<vector>
#include<iostream>
#include<algorithm>
#include<bitset>
#include<climits>
#include<list>
#include<iomanip>
#include<stack>
#include<set>
using namespace std;
int tail,cnt,num;
int dfn[1010],low[1010],belong[1010],head[1010],w[1010],mn[1010];
bool isin[1010],vis[1010];
stack<int>sk;
struct Edge
{
int from,to,next;
}edge[4010];
void add(int from,int to)
{
edge[tail].from=from;
edge[tail].to=to;
edge[tail].next=head[from];
head[from]=tail++;
}
void dfs(int from)
{
isin[from]=1;
sk.push(from);
dfn[from]=low[from]=++num;
for(int i=head[from];i!=-1;i=edge[i].next)
{
int to=edge[i].to;
if(!dfn[to])
{
dfs(to);
low[from]=min(low[from],low[to]);
}
else if(isin[to])
low[from]=min(low[from],dfn[to]);
}
if(low[from]==dfn[from])
{
mn[++cnt]=w[from];
while(1)
{
int t=sk.top();
sk.pop();
belong[t]=cnt;
isin[t]=0;
mn[cnt]=min(mn[cnt],w[t]);
if(t==from)
break;
}
}
}
int main()
{
int n,m;
while(scanf("%d%d",&n,&m)!=EOF)
{
for(int i=1;i<=n;i++)
scanf("%d",w+i);
tail=0;
memset(head,-1,sizeof(head));
for(int i=0;i<m;i++)
{
int a,b;
scanf("%d%d",&a,&b);
add(a,b);
}
num=cnt=0;
memset(dfn,0,sizeof(dfn));
for(int i=1;i<=n;i++)
if(!dfn[i])
dfs(i);
memset(vis,0,sizeof(vis));
for(int i=0;i<m;i++)
if(belong[edge[i].from]!=belong[edge[i].to])
vis[belong[edge[i].to]]=1;
int ans1=0,ans2=0;
for(int i=1;i<=cnt;i++)
if(!vis[i])
{
ans1++;
ans2+=mn[i];
}
printf("%d %d\n",ans1,ans2);
}
}
hdu4738
http://acm.hdu.edu.cn/showproblem.php?pid=4738
题意:所有边都有个权值,求权值最小的桥。
特判:如果给出不止一个连通图,输出0;如果最小的桥为0,输出1。
#include<map>
#include<string>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<queue>
#include<vector>
#include<iostream>
#include<algorithm>
#include<bitset>
#include<climits>
#include<list>
#include<iomanip>
#include<stack>
#include<set>
using namespace std;
int tail,num,ans;
int dfn[1010],low[1010],head[1010];
struct Edge
{
int id,val,to,next;
}edge[2000000];
void add(int from,int to,int id,int val)
{
edge[tail].id=id;
edge[tail].val=val;
edge[tail].to=to;
edge[tail].next=head[from];
head[from]=tail++;
}
void dfs(int from,int fa)
{
dfn[from]=low[from]=++num;
for(int i=head[from];i!=-1;i=edge[i].next)
{
int to=edge[i].to;
if(edge[i].id==fa)
continue;
if(!dfn[to])
{
dfs(to,edge[i].id);
low[from]=min(low[from],low[to]);
if(low[to]>dfn[from])
ans=min(ans,edge[i].val);
}
else
low[from]=min(low[from],dfn[to]);
}
}
int main()
{
int n,m;
while(scanf("%d%d",&n,&m)!=EOF)
{
if(n==0&&m==0)
return 0;
tail=0;
memset(head,-1,sizeof(head));
for(int i=0;i<m;i++)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b,i,c);
add(b,a,i,c);
}
int sum=0;
ans=INT_MAX;
num=0;
memset(dfn,0,sizeof(dfn));
for(int i=1;i<=n;i++)
if(!dfn[i])
{
dfs(i,-1);
sum++;
}
printf("%d\n",sum>1?0:ans==INT_MAX?-1:ans==0?1:ans);
}
}
hdu4612
http://acm.hdu.edu.cn/showproblem.php?pid=4612
题意:随意加一条边,让图的桥的数量最小。
做法:双连通分量缩点,重新建图,形成一棵树,求出树的直径,双连通分量的数量减去直径上的点就是答案。要开下栈,用C++交。
#pragma comment(linker,"/STACK:102400000,102400000")
#include<map>
#include<string>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<queue>
#include<vector>
#include<iostream>
#include<algorithm>
#include<bitset>
#include<climits>
#include<list>
#include<iomanip>
#include<stack>
#include<set>
using namespace std;
struct Edge
{
int id,u,v,next;
}edge[4000010];
int head[200010],tail;
void add(int u,int v,int id)
{
edge[tail].u=u;
edge[tail].v=v;
edge[tail].id=id;
edge[tail].next=head[u];
head[u]=tail++;
}
stack<int>sk;
int dfn[200010],low[200010],belong[200010],cnt,num;
void dfs(int u,int fa)
{
sk.push(u);
dfn[u]=low[u]=++cnt;
for(int i=head[u];i!=-1;i=edge[i].next)
{
if(edge[i].id==fa)
continue;
int v=edge[i].v;
if(!dfn[v])
{
dfs(v,edge[i].id);
low[u]=min(low[u],low[v]);
}
else
low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u])
{
num++;
while(1)
{
int t=sk.top();
sk.pop();
belong[t]=num;
if(t==u)
break;
}
}
}
int mx,node,dp[200010];
void dfs(int u)
{
if(dp[u]>mx)
{
mx=dp[u];
node=u;
}
for(int i=head[u];i!=-1;i=edge[i].next)
if(!dp[edge[i].v])
{
dp[edge[i].v]=dp[u]+1;
dfs(edge[i].v);
}
}
int main()
{
int n,m;
while(scanf("%d%d",&n,&m)!=EOF)
{
if(n==0&&m==0)
return 0;
tail=0;
memset(head,-1,sizeof(head));
for(int i=0;i<m;i++)
{
int u,v;
scanf("%d%d",&u,&v);
add(u,v,i);
add(v,u,i);
}
cnt=num=0;
memset(dfn,0,sizeof(dfn));
dfs(1,-1);
memset(head,-1,sizeof(head));
m<<=1;
for(int i=0;i<m;i++)
if(belong[edge[i].u]!=belong[edge[i].v])
{
add(belong[edge[i].u],belong[edge[i].v],0);
add(belong[edge[i].v],belong[edge[i].u],0);
}
memset(dp,0,sizeof(dp));
dp[1]=1;
mx=-1;
dfs(1);
mx=-1;
memset(dp,0,sizeof(dp));
dp[node]=1;
dfs(node);
printf("%d\n",num-mx);
}
}