Tarjan算法
Tarjan算法与无向图连通性
缩点
int tdfn,c[N],dfn[N],low[N],tak[N];
bool ins[N];
void tarjan(int u)
{
dfn[u]=low[u]=++tdfn;
tak[++tak[0]]=u;
ins[u]=1;//在栈里
for(int i=Tail[u];i;i=E[i].pre)
{
int v=E[i].v;
if(!dfn[v]) {tarjan(v);low[u]=min(low[u],low[v]);}
else if(ins[v]) low[u]=min(low[u],dfn[v]);
}
if(low[u]==dfn[u])
{
++c[0];
while(tak[tak[0]+1]!=u) c[tak[tak[0]]]=c[0],ins[tak[tak[0]--]]=0;
}
}
Grass Cownoisseur(有向图任意跑一条反边的最长路)
(grass.cpp/1s/128M)
题目描述
约翰有 块草场,编号 到 ,这些草场由若干条单行道相连。奶牛贝西是美味牧草的鉴赏家,她想到
达尽可能多的草场去品尝牧草。
贝西总是从 号草场出发,最后回到 号草场。她想经过尽可能多的草场,贝西在通一个草场只吃一次
草,所以一个草场可以经过多次。因为草场是单行道连接,这给贝西的品鉴工作带来了很大的不便,贝
西想偷偷逆向行走一次,但最多只能有一次逆行。请问贝西最多能吃到多少个草场的牧草。
Tarjan缩点建新图。
所有的可以从x出发又回到x的点都和x缩成一个节点,故新图没有节点能从x出发还回到x点,即x不可能被算两次。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,m,ans;
int tE,te,Tail[N],tail[N*2];
struct e_
{
int v,pre;
}E[N],e[N*3];
inline void add_(int u,int v)
{
E[++tE]=(e_){v,Tail[u]};Tail[u]=tE;
}
inline void add(int u,int v)
{
e[++te]=(e_){v,tail[u]};tail[u]=te;
e[++te]=(e_){v+N,tail[u+N]};tail[u+N]=te;
e[++te]=(e_){u+N,tail[v]};tail[v]=te;
}
int tdfn,c[N],dfn[N],low[N],tak[N],siz[N*2];
bool ins[N];
void tarjan(int u)
{
dfn[u]=low[u]=++tdfn;
tak[++tak[0]]=u;
ins[u]=1;//在栈里
for(int i=Tail[u];i;i=E[i].pre)
{
int v=E[i].v;
if(!dfn[v])
{
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(ins[v]) low[u]=min(low[u],dfn[v]);
}
if(low[u]==dfn[u])
{
++c[0];
while(tak[tak[0]+1]!=u)
siz[c[0]]++,
c[tak[tak[0]]]=c[0],
ins[tak[tak[0]--]]=0;
siz[c[0]+N]=siz[c[0]];
}
}
void build()
{
for(int i=1;i<=n;++i)
if(!dfn[i]) tarjan(i);
for(int i=1;i<=n;++i)
for(int j=Tail[i];j;j=E[j].pre)
{
int v=E[j].v;
if(c[i]==c[v]) continue;
add(c[i],c[v]);
}
}
int D[N*2];
bool vis[N*2];
void spfa()
{
deque<int>q;
q.push_back(c[1]);
while(!q.empty())
{
int u=q.front();
q.pop_front();
vis[u]=0;
for(int i=tail[u];i;i=e[i].pre)
{
int v=e[i].v,w=siz[v];
if(D[v]<D[u]+w)
{
D[v]=D[u]+w;
if(!vis[v]) vis[v]=1,q.push_back(v);
}
}
}
}
int main()
{
// freopen("grass.in","r",stdin);
// freopen("grass.out","w",stdout);
scanf("%d %d",&n,&m);
for(int i=1,u,v;i<=m;++i) scanf("%d %d",&u,&v),add_(u,v);
build();//GCC新图
spfa();//分层图最长路跑一跑
printf("%d",max(D[c[1]],D[c[1]+N]));
}
割边判定
1.对于每个点,dfn[x]表示其dfs序, low[x]表示从树以外的边可到的dfs序最小的点。
2.对于每条边\((u,v)\),如果dfn[u]<low[v],即从v无法到达u之前的节点,即只有该边能将u,v连接,去掉该边图必定会分裂。
注意:由于可能出现重边,所以每次dfs时记录进来的边,不走这条边,走和这条边相重叠的边是可以的。
void tarjan(int u,int fa)
{
dfn[u]=low[u]=++tdfn;
for(int i=tail[u];i;i=e[i].pre)
{
int v=e[i].v;
if(!dfn[v])
{
tarjan(v,i);
low[u]=min(low[u],low[v]);
if(dfn[u]<low[v]) bri[i]=bri[i^1]=1;
}
else if(i!=(fa^1)) low[u]=min(low[u],dfn[v]);
}
}
int main()
{
...
for(int i=1;i<=n;++i)
if(!dfn[i]) tarjan(i,0);
...
}
网络
给定一张N个点M条边的无向连通图,然后执行Q次操作,每次向图中添加一条边,并且询问当前无向图中“桥”的数量。
输入格式
输入包含多组测试数据。
每组测试数据,第一行包含两个整数N和M。
接下来M行,每行包含两个整数A和B,表示点A和点B之间有一条边,点的编号为1~N。
接下来一行,包含整数Q。
在接下来Q行,每行包含两个整数A和B,表示在A和B之间加一条边。
当输入0 0时表示输入终止。
输出格式
每组数据第一行输出“Case x:”,其中x为组别编号,从1开始。
接下来Q行,每行输出一个整数,表示一次询问的结果。
每组数据输出完毕后,输出一个空行。
数据范围
1≤N≤100000
N−1≤M≤200000,
1≤A≠B≤N,
1≤Q≤1000
输入样例:
3 2
1 2
2 3
2
1 2
1 3
4 4
1 2
2 1
2 3
1 4
2
1 2
3 4
0 0
输出样例:
输出样例:
Case 1:
1
0
Case 2:
2
0
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5,M=2e5+5;
int n,m,k,te,tc,cas,tdfn,sum,c[N],tail[N],dfn[N],low[N],dep[N],pre[N];
bool bri[M<<1],vis[N];
struct e_
{
int v,pre;
}e[M<<1];
void init()
{
te=3;
tc=tdfn=sum=0;
memset(c,0,sizeof(c));
memset(dfn,0,sizeof(dfn));
memset(bri,0,sizeof(bri));
memset(vis,0,sizeof(vis));
memset(pre,0,sizeof(pre));
memset(tail,0,sizeof(tail));
}
inline void add(int u,int v)
{
e[++te]=(e_){v,tail[u]};
tail[u]=te;
}
void tarjan(int u,int fa)
{
dfn[u]=low[u]=++tdfn;
for(int i=tail[u];i;i=e[i].pre)
{
int v=e[i].v;
if(!dfn[v])
{
tarjan(v,i);
low[u]=min(low[u],low[v]);
if(dfn[u]<low[v]) sum++,bri[i]=bri[i^1]=vis[v]=1;
}
else if(i!=(fa^1)) low[u]=min(low[u],dfn[v]);
}
}
void dfs(int u)
{
c[u]=tc;
for(int i=tail[u];i;i=e[i].pre)
{
int v=e[i].v;
if(!c[v]&&!bri[i]) dfs(v);
}
}
void dfs_(int u)
{
for(int i=tail[u];i;i=e[i].pre)
{
int v=e[i].v;
if(!pre[v])
{
dep[v]=dep[u]+1;
pre[v]=u;
dfs_(v);
}
}
}
void lca(int u,int v)
{
if(dep[u]<dep[v]) swap(u,v);
while(dep[u]>dep[v])
{
if(vis[u]) sum--,vis[u]=0;
u=pre[u];
}
while(u!=v)
{
if(vis[u]) sum--,vis[u]=0;
if(vis[v]) sum--,vis[v]=0;
u=pre[u];
v=pre[v];
}
}
int main()
{
while(~scanf("%d %d",&n,&m)&&n)
{
printf("Case %d:\n",++cas);
init();
for(int i=1,u,v;i<=m;++i)
{
scanf("%d %d",&u,&v);
add(u,v);
add(v,u);
}
for(int i=1;i<=n;++i)
if(!dfn[i]) tarjan(i,0);
for(int i=1;i<=n;++i)
if(!c[i])
{
++tc;
dfs(i);
}
int tte=te;
te=1;
memset(vis,1,sizeof(vis));
memset(tail,0,sizeof(tail));
for(int i=4;i<=tte;++i)
if(bri[i]) add(c[e[i].v],c[e[i^1].v]);
dfs_(1);
scanf("%d",&k);
for(int i=1,a,b;i<=k;++i)
{
scanf("%d %d",&a,&b);
if(c[a]!=c[b]) lca(c[a],c[b]);
printf("%d\n",sum);
}
printf("\n");
}
}
割点判定
1.对于每个点,dfn[x]表示其dfs序, low[x]表示从树以外的边可到的dfs序最小的点。
2.对于每组点\((u,v)\),如果dfn[u]<=low[v],即从v无法到达u之前的节点,即只有u能到v连接,去掉该点图必定会分裂。
对于根节点,当且仅当至少有两个点满足上述条件,即至少有两个子树时,根节点是割点。
void tarjan(int u)
{
dfn[u]=low[u]=++tdfn;
int cnt=0;
for(int i=tail[u];i;i=e[i].pre)
{
int v=e[i].v;
if(!dfn[v])
{
tarjan(v,i);
low[u]=min(low[u],low[v]);
if(dfn[u]<=low[v])
{
cnt++;
if(u!=root||cnt>1) cut[u]=1;
}
}
else low[u]=min(low[u],dfn[v]);
}
}
int main()
{
...
for(int i=1;i<=n;++i)
if(!dfn[i]) tarjan(i,0);
...
}
B城
B城有 n 个城镇,m 条双向道路。
每条道路连结两个不同的城镇,没有重复的道路,所有城镇连通。
把城镇看作节点,把道路看作边,容易发现,整个城市构成了一个无向图。
输入格式
第一行包含两个整数 n 和 m。
接下来m行,每行包含两个整数 a 和 b,表示城镇 a 和 b 之间存在一条道路。
输出格式
输出共n行,每行输出一个整数。
第 i 行输出的整数表示把与节点 i 关联的所有边去掉以后(不去掉节点 i 本身),无向图有多少个有序点(x,y),满足 x 和 y 不连通。
数据范围
n≤100000,m≤500000
输入样例:
5 5
1 2
2 3
1 3
3 4
4 5
输出样例:
8
8
16
14
8
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e5+5,M=5e5+5;
ll n,root,tdfn,m,te,tail[N],dfn[N],low[N],ans[N],siz[N];
bool cut[N];
struct e_
{
int v,pre;
}e[M<<1];
inline ll read()
{
int res=0;
char dd=getchar();
while(dd<'0'||dd>'9') dd=getchar();
while(dd>='0'&&dd<='9') res=(res<<1)+(res<<3)+dd-'0',dd=getchar();
return res;
}
inline void add(int u,int v)
{
e[++te]=(e_){v,tail[u]};
tail[u]=te;
}
void tarjan(int u)
{
int cntt=0;
ll sum=0;
siz[u]=1;
dfn[u]=low[u]=++tdfn;
for(int i=tail[u];i;i=e[i].pre)
{
int v=e[i].v;
if(!dfn[v])
{
tarjan(v);
siz[u]+=siz[v];
low[u]=min(low[u],low[v]);
if(dfn[u]<=low[v])
{
cntt++;
ans[u]+=siz[v]*(n-siz[v]);
sum+=siz[v];
if(u!=root||cntt>1) cut[u]=1;
}
}
else low[u]=min(low[u],dfn[v]);
}
if(cut[u]) ans[u]+=(n-sum-1)*(sum+1)+(n-1);
else ans[u]=(n-1)*2;
}
int main()
{
n=read();
m=read();
for(int i=1,u,v;i<=m;++i)
{
u=read();
v=read();
add(u,v);
add(v,u);
}
for(int i=1;i<=n;++i)
if(!dfn[i]){root=i;tarjan(i);}
for(int i=1;i<=n;++i) printf("%lld\n",ans[i]);
}

浙公网安备 33010602011771号