少女祈祷中

[题解]ILY.

简化题意:

给出一张有向图,若干询问,每次询问给出一条边,问加入这条边后有向图中共有多少个强连通分量。

显然本来是强联通分量的子图在加入一条边后还是强联通分量,所以可以先缩点。

缩点后原图变为一张DAG,考虑什么情况下加入边 \(u \to v\) 后强连通分量会减少:

\(U,V\) 分别为 \(u,v\) 所属的强联通分量,那么加入边 \(u \to v\) 后强联通分量会减少当且仅当:

  • \(U\) 不可达 \(V\) :加入边 \(u \to v\) 后图的连通性不变

  • \(V\) 可达 \(U\) :加入边 \(u \to v\)\(U \leadsto V\) ,使得在图中增加一个包含 \(U,V\) 的环, \(U,V\) 合并到同一个强联通分量中

综上,加入 \(u \to v\) 后SCC会减少当且仅当 \(U\) 不可达 \(V\)\(V\) 可达 \(U\)

这两个条件可以通过传递闭包判断,现在需要解决每次加边一共会减少多少个SCC。

显然,若加入边 \(u \to v\) 后SCC会减少,那么 \(V\)\(U\) 的路径上的所有SCC都会合并为一个SCC,且其他SCC不受影响。因此,若加入边 \(u \to v\) 后SCC减少,则减少的数量为 \(V\)\(U\) 上所有SCC(包括 \(U,V\) )的数量 \(-1\)

直接计算这条路径上所有SCC的数量是困难的。不妨考虑从 \(V\)\(U\) 的路径上每个SCC的性质:显然地,某个SCC \(W\)\(V\)\(U\) 的路径上当且仅当 \(V\) 可达 \(W\)\(W\) 可达 \(U\) 。也就是说,设从 \(V\) 出发可以到达的SCC的集合为 \(S_V\) ,可以到达 \(U\) 的SCC的集合为 \(T_U\) ,那么从 \(V\)\(U\) 的路径上的SCC的数量就是 \(|S_V \cap T_U|\)

通过传递闭包得出 \(S_V,T_U\) 是容易的。

由于直接传递闭包的时间复杂度是 \(O(n^3)\) 的,所以我们需要bitset优化,时间复杂度为 \(O( \frac{n^3}{ \omega } )\)

4ce05e80a5d0ec144dae078dda7ef381

#include <cstdio>
#include <vector>
#include <bitset>
#include <algorithm>
using namespace std;
struct Edge{int to,next;};
int n,m,q;
vector<vector<int>> ans;
int SCC,cnt_dfn;
vector<int> dfn,low,scc,head;
vector<bool> vis;
vector<Edge> edge;
int top;
vector<int> stack;
vector<bitset<805>> S,T;
inline int read()
{
    int x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    return x*f;
}
inline void link(const int u,const int v) {edge.push_back({v,head[u]}),head[u]=(int)edge.size()-1;}
inline void tarjan(const int u)
{
	dfn[u]=low[u]=++cnt_dfn,stack[++top]=u,vis[u]=true;
	for(int i=head[u];i;i=edge[i].next)
	{
		int v=edge[i].to;
		if(!dfn[v]) tarjan(v),low[u]=min(low[u],low[v]);
		else if(vis[v]) low[u]=min(low[u],dfn[v]);
	}
	if(dfn[u]==low[u])
	{
		SCC++;
		while(u^stack[top+1]) scc[stack[top]]=SCC,vis[stack[top]]=false,top--;
	}
}
int main()
{
	n=read(),m=read(),q=read();
	head.resize(n+5),edge.push_back({0});
	for(int i=1,u,v;i<=m;++i) u=read(),v=read(),link(u,v);
	dfn.resize(n+5),low.resize(n+5),scc.resize(n+5),vis.resize(n+5);
	stack.resize(n+5);
	for(int u=1;u<=n;++u)
		if(!dfn[u]) top=0,tarjan(u);
	S.resize(SCC+5);
	for(int i=1;i<=n;++i)
		for(int j=head[i];j;j=edge[j].next)
		{
			int u=scc[i],v=scc[edge[j].to];
			if(u^v) S[u].set(v);
		}
	for(int u=1;u<=SCC;++u) S[u].set(u);
	for(int k=1;k<=SCC;++k)
		for(int u=1;u<=SCC;++u)
			if(S[u][k]) S[u]|=S[k];
	T.resize(SCC+5);
	for(int u=1;u<=SCC;++u)
		for(int v=1;v<=SCC;++v)
			if(S[u][v]) T[v].set(u);
	ans.resize(SCC+5,vector<int>(SCC+5));
	for(int u=1;u<=SCC;++u)
		for(int v=1;v<=SCC;++v) ans[u][v]=(S[v][u])?(SCC-(S[v]&T[u]).count()+1):(SCC);
	while(q--)
	{
		int u=read(),v=read();
		printf("%d\n",ans[scc[u]][scc[v]]);
	}
	return 0;
}
posted @ 2026-02-03 20:11  _待宵  阅读(0)  评论(0)    收藏  举报