ybtAu「图论」第5章 强连通分量
A. 【例题1】受欢迎的牛
缩点,存在受欢迎的牛当且仅当只有一个点的出度为 \(0\),此时该点所包含的原图的节点数量就是答案。
#include <iostream>
#define N 100005
int n,m,c[N],hed[N],tal[N],nxt[N],cnte,siz[N],low[N],dfn[N],st[N],tp,idx,cnt,out[N];
void adde(int u,int v) {tal[++cnte]=v,nxt[cnte]=hed[u],hed[u]=cnte;}
void dfs(int x)
{
dfn[st[++tp]=x]=low[x]=++idx;
for(int i=hed[x];i;i=nxt[i])
{
if(!dfn[tal[i]]) dfs(tal[i]),low[x]=std::min(low[x],low[tal[i]]);
else if(!c[tal[i]]) low[x]=std::min(low[x],low[tal[i]]);
}
if(low[x]==dfn[x]) for(siz[c[x]=++cnt]=1;st[tp--]!=x;siz[cnt]++) c[st[tp+1]]=cnt;
}
int main()
{
std::ios::sync_with_stdio(0);
std::cin.tie(0),std::cout.tie(0);
std::cin>>n>>m;
for(int i=1,u,v;i<=m;i++) std::cin>>u>>v,adde(u,v);
for(int i=1;i<=n;i++) if(!dfn[i]) dfs(i);
for(int i=1;i<=n;i++) for(int j=hed[i];j;j=nxt[j]) if(c[i]!=c[tal[j]]) out[c[i]]++;
int ans=-1;
for(int i=1;i<=cnt;i++) if(!out[i]) ans=(ans==-1)?siz[i]:0;
std::cout<<ans;
}
B. 【例题2】最大半连通子图
缩点,最长链就是第一问答案,在 DP 过程中对每个节点记录到它的最长链个数 \(g_i\),易得第二问答案。
#include <iostream>
#include <list>
#define N 2000005
std::list<int> q;
int n,m,p,c[N],siz[N],vis[N],hed[N],deh[N],tal[N],nxt[N],cnte,dfn[N],low[N],st[N],tp,cnt,idx,in[N],f[N],g[N];
void adde(int u,int v) {tal[++cnte]=v,nxt[cnte]=hed[u],hed[u]=cnte;}
void edda(int u,int v) {tal[++cnte]=v,nxt[cnte]=deh[u],deh[u]=cnte;}
void dfs(int x)
{
dfn[st[++tp]=x]=low[x]=++idx;
for(int i=hed[x];i;i=nxt[i])
{
if(!dfn[tal[i]]) dfs(tal[i]),low[x]=std::min(low[x],low[tal[i]]);
else if(!c[tal[i]]) low[x]=std::min(low[x],low[tal[i]]);
}
if(low[x]==dfn[x]) for(siz[c[x]=++cnt]=1;st[tp--]!=x;siz[cnt]++) c[st[tp+1]]=cnt;
}
int main()
{
std::ios::sync_with_stdio(0);
std::cin.tie(0),std::cout.tie(0);
std::cin>>n>>m>>p;
for(int i=1,u,v;i<=m;i++) std::cin>>u>>v,adde(u,v);
for(int i=1;i<=n;i++) if(!dfn[i]) dfs(i);
for(int i=1;i<=n;i++) for(int j=hed[i];j;j=nxt[j]) if(c[i]!=c[tal[j]]) edda(c[i],c[tal[j]]),in[c[tal[j]]]++;
for(int i=1;i<=cnt;i++) if(!in[i]) q.push_back(i),f[i]=siz[i]%p,g[i]=1;
int maxc=0,ans=0;
while(!q.empty())
{
int u=q.front();
q.pop_front();
if(f[u]>maxc) maxc=f[u],ans=0;
if(f[u]==maxc) (ans+=g[u])%=p;
for(int i=deh[u];i;i=nxt[i])
{
if(vis[tal[i]]!=u)
{
vis[tal[i]]=u;
if(f[tal[i]]<f[u]+siz[tal[i]]) f[tal[i]]=f[u]+siz[tal[i]],g[tal[i]]=0;
if(f[tal[i]]==f[u]+siz[tal[i]]) (g[tal[i]]+=g[u])%=p;
}
if(!--in[tal[i]]) q.push_back(tal[i]);
}
}
std::cout<<maxc<<'\n'<<ans;
}
Kosaraju
下文有部分代码使用的缩点算法不是 Tarjan 而是 Kosaraju。该算法需要建出原图和反图,因此看到类似代码时不要惊讶。
C. 网络协议
缩点,入度为 \(0\) 的节点数就是第一问答案。
对于第二问,要求把该图变成一个强连通分量需要添加的最小边数。
令 \(S\) 表示入度为 \(0\) 的节点个数,\(T\) 表示出度为 \(0\) 的节点个数。
如果只剩下一个点,那么答案为 \(0\);否则,由于在一个强连通分量中,\(S=T=0\);而对于缩点得到的 DAG,显然 \(S\neq0\) 且 \(T\neq0\),而每添加一条边,最多可以使 \(S\) 和 \(T\) 同时减少 \(1\),于是答案为 \(\max(S,T)\)。
#include <iostream>
#define N 105
#define M 20005
int hed[N],deh[N],ded[N],tal[M],nxt[M],cnte,n,x,cnt,col[N],in[N],out[N];
void de(int u,int v) {tal[++cnte]=v,nxt[cnte]=hed[u],hed[u]=cnte;}
void ed(int u,int v) {tal[++cnte]=v,nxt[cnte]=deh[u],deh[u]=cnte;}
void adde(int u,int v) {tal[++cnte]=v,nxt[cnte]=ded[u],ded[u]=cnte;}
namespace SCC
{
bool vis[N];
int li[N],idx;
void dfs1(int x)
{
vis[x]=1;
for(int i=hed[x];i;i=nxt[i]) if(!vis[tal[i]]) dfs1(tal[i]);
li[++idx]=x;
}
void dfs2(int x)
{
col[x]=cnt;
for(int i=deh[x];i;i=nxt[i]) if(!col[tal[i]]) dfs2(tal[i]);
}
void kosaraju()
{
cnt=idx=0;
for(int i=1;i<=n;i++) if(!vis[i]) dfs1(i);
for(int i=n;i>=1;i--) if(!col[li[i]]) cnt++,dfs2(li[i]);
}
};
signed main()
{
std::ios::sync_with_stdio(0);
std::cin.tie(0),std::cout.tie(0);
std::cin>>n;
for(int i=1;i<=n;i++) for(int x;;)
{
std::cin>>x;
if(!x) break;
de(i,x),ed(x,i);
}
SCC::kosaraju();
for(int x=1;x<=n;x++) for(int i=hed[x];i;i=nxt[i])
if(col[x]!=col[tal[i]]) adde(col[x],col[tal[i]]),in[col[tal[i]]]++,out[col[x]]++;
int s=0,t=0;
for(int i=1;i<=cnt;i++) s+=!in[i],t+=!out[i];
std::cout<<s<<'\n'<<(cnt==1?0:std::max(s,t));
}
D. 抢掠计划
缩点,求以包含市中心的节点为起点,以包含酒吧的节点为终点的最长链。
由于包含市中心的节点不一定是图的源点,所以不能直接使用拓扑排序处理,需要使用 SPFA 来求最长链。
#include <iostream>
#include <queue>
#define N 1500005
#define int long long
std::queue<int> q;
int hed[N],deh[N],ded[N],tal[N],nxt[N],cnte,cnt,col[N],f[N],n,m,a[N],S,bar[N],ex[N],sz[N];
void de(int u,int v) {tal[++cnte]=v,nxt[cnte]=hed[u],hed[u]=cnte;}
void ed(int u,int v) {tal[++cnte]=v,nxt[cnte]=deh[u],deh[u]=cnte;}
void adde(int u,int v) {tal[++cnte]=v,nxt[cnte]=ded[u],ded[u]=cnte;}
namespace SCC
{
int li[N],vis[N],idx;
void dfs1(int x) {vis[x]=1;for(int i=hed[x];i;i=nxt[i]) if(!vis[tal[i]]) dfs1(tal[i]);li[++idx]=x;}
void dfs2(int x) {col[x]=cnt;for(int i=deh[x];i;i=nxt[i]) if(!col[tal[i]]) dfs2(tal[i]);}
void kosaraju() {for(int i=1;i<=n;i++) if(!vis[i]) dfs1(i);for(int i=n;i>=1;i--) if(!col[li[i]]) cnt++,dfs2(li[i]);}
};
signed main()
{
std::ios::sync_with_stdio(0);
std::cin.tie(0),std::cout.tie(0);
std::cin>>n>>m;
for(int i=1,u,v;i<=m;i++) std::cin>>u>>v,de(u,v),ed(v,u);
for(int i=1;i<=n;i++) std::cin>>a[i];
int p;
std::cin>>S>>p;
SCC::kosaraju(),S=col[S];
for(int i=1;i<=n;i++) sz[col[i]]+=a[i];
for(int i=1,x;i<=p;i++) std::cin>>x,bar[col[x]]=1;
for(int x=1;x<=n;x++) for(int i=hed[x];i;i=nxt[i]) if(col[x]!=col[tal[i]]) adde(col[x],col[tal[i]]);
int ans=0;
q.push(S),f[S]=sz[S];
while(!q.empty())
{
int u=q.front();
q.pop(),ex[u]=0;
if(bar[u]) ans=std::max(ans,f[u]);
for(int i=ded[u];i;i=nxt[i]) if(f[tal[i]]<f[u]+sz[tal[i]])
{
f[tal[i]]=f[u]+sz[tal[i]];
if(!ex[tal[i]]) ex[tal[i]]=1,q.push(tal[i]);
}
}
std::cout<<ans;
}
E. 稳定婚姻
考虑对每对夫妻,女向男连边;对每对情侣,男向女连边。
如果一对夫妻在一个强连通分量里,那么这对婚姻是不安全的。
由于该图的性质,如果存在环,那么一定是偶环。当一对夫妻在一个强连通分量里时,如果断掉这对夫妻所在的边,那么可以从丈夫开始,交替地走情侣边和夫妻边,所走的情侣边的两端成为一对情侣,最后该强连通分量里的所有点都被覆盖,而不在该强连通分量里的点不受影响;如果这对夫妻不在强连通分量里,那么不能完成上述过程,这对婚姻就是安全的。
#include <iostream>
#include <unordered_map>
#define N 60005
std::unordered_map<std::string,int> mp;
int hed[N],deh[N],tal[N],nxt[N],cnte,col[N],cnt,n,m,cn;
void de(int u,int v) {tal[++cnte]=v,nxt[cnte]=hed[u],hed[u]=cnte;}
void ed(int u,int v) {tal[++cnte]=v,nxt[cnte]=deh[u],deh[u]=cnte;}
namespace SCC
{
int li[N],vis[N],idx;
void dfs1(int x) {vis[x]=1;for(int i=hed[x];i;i=nxt[i]) if(!vis[tal[i]]) dfs1(tal[i]);li[++idx]=x;}
void dfs2(int x) {col[x]=cnt;for(int i=deh[x];i;i=nxt[i]) if(!col[tal[i]]) dfs2(tal[i]);}
void kosaraju() {for(int i=2;i<=cn;i++) if(!vis[i]) dfs1(i);for(int i=idx;i>=1;i--) if(!col[li[i]]) cnt++,dfs2(li[i]);}
};
signed main()
{
std::ios::sync_with_stdio(0);
std::cin.tie(0),std::cout.tie(0);
std::cin>>n;
std::string x,y;
cn=1;
for(int i=1;i<=n;i++) std::cin>>x>>y,mp[x]=++cn,mp[y]=++cn,de(cn-1,cn),ed(cn,cn-1);
std::cin>>m;
for(int i=1;i<=m;i++) std::cin>>x>>y,de(mp[y],mp[x]),ed(mp[x],mp[y]);
SCC::kosaraju();
for(int i=1;i<=n;i++)
{
if(col[i<<1]==col[i<<1|1]) std::cout<<"Unsafe\n";
else std::cout<<"Safe\n";
}
}
F. 宫室宝藏
由于总格子数很多,而有传送门的格子数并不多,所以只考虑这些有传送门的格子之间的连边。
对于“任意门”,可以使用 map 直接连边;而对于后两种,如果所有有传送门的格子都在同一行或同一列,那么边数将会达到 \(O(n^2)\) 级别,是不能接受的。
所以对于后两种,可以给每一行和每一列新开一个节点,向该行或列有传送门的格子连边;对于“横天门”,由该格子向该行连边;对于“纵寰门”,由该格子向该列连边。
缩点跑最长链即可。注意在计算点权的时候只把有传送门的格子的点加进去。
#include <iostream>
#include <vector>
#include <unordered_map>
#include <list>
#define N 3000005
//1~n n+1~n+R n+R+1~n+R+C
int n,R,C,hed[N],deh[N],tal[N],nxt[N],cnte,d[N];
int tx[8]={-1,-1,-1,0,0,1,1,1};
int ty[8]={-1,0,1,-1,1,-1,0,1};
int c[N],siz[N],dfn[N],low[N],st[N],tp,idx,cnt,in[N],f[N];
void adde(int u,int v) {tal[++cnte]=v,nxt[cnte]=hed[u],hed[u]=cnte;}
void edda(int u,int v) {tal[++cnte]=v,nxt[cnte]=deh[u],deh[u]=cnte;}
std::pair<int,std::pair<int,int> > a[N];
std::unordered_map<int,std::unordered_map<int,int> > mp;
std::list<int> q;
void dfs(int x)
{
dfn[st[++tp]=x]=low[x]=++idx;
for(int i=hed[x];i;i=nxt[i])
{
if(!dfn[tal[i]]) dfs(tal[i]),low[x]=std::min(low[x],low[tal[i]]);
else if(!c[tal[i]]) low[x]=std::min(low[x],low[tal[i]]);
}
if(low[x]==dfn[x]) for(siz[c[x]=++cnt]=d[x];st[tp--]!=x;) siz[c[st[tp+1]]=cnt]+=d[st[tp+1]];
}
int main()
{
std::ios::sync_with_stdio(0);
std::cin.tie(0),std::cout.tie(0);
std::cin>>n>>R>>C;
for(int i=1,x,y,op;i<=n;i++)
{
std::cin>>x>>y>>op;
a[i]={op,{x,y}};
mp[x][y]=i,d[i]=1;
}
for(int i=1;i<=n;i++)
{
int op=a[i].first,x=a[i].second.first,y=a[i].second.second;
adde(n+x,i),adde(n+R+y,i);
if(op==1) adde(i,n+x);
if(op==2) adde(i,n+R+y);
if(op==3) for(int j=0;j<8;j++)
{
int nx=x+tx[j],ny=y+ty[j];
if(mp[nx][ny]) adde(i,mp[nx][ny]);
}
}
for(int i=1;i<=n+R+C;i++) if(!dfn[i]) dfs(i);
for(int i=1;i<=n+R+C;i++) for(int j=hed[i];j;j=nxt[j]) if(c[i]!=c[tal[j]]) edda(c[i],c[tal[j]]),in[c[tal[j]]]++;
for(int i=1;i<=cnt;i++) if(!in[i]) q.push_back(i),f[i]=siz[i];
int ans=0;
while(!q.empty())
{
int u=q.front();
q.pop_front();
ans=std::max(ans,f[u]);
for(int i=deh[u];i;i=nxt[i])
{
f[tal[i]]=std::max(f[tal[i]],f[u]+siz[tal[i]]);
if(!--in[tal[i]]) q.push_back(tal[i]);
}
}
std::cout<<ans;
}

浙公网安备 33010602011771号