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;
}
posted @ 2025-06-25 14:00  整齐的艾萨克  阅读(5)  评论(0)    收藏  举报