【洛谷P7737】庆典

题目

题目链接:https://www.luogu.com.cn/problem/P7737
C 国是一个繁荣昌盛的国家,它由 \(n\) 座城市和 \(m\) 条有向道路组成,城市从 \(1\)\(n\) 编号。如果从 \(x\) 号城市出发,经过若干条道路后能到达 \(y\) 号城市,那么我们称 \(x\) 号城市可到达 \(y\) 号城市,记作 \(x\Rightarrow y\)。C 国的道路有一个特点:对于三座城市 \(x\)\(y\)\(z\),若 \(x\Rightarrow z\)\(y\Rightarrow z\),那么有 \(x\Rightarrow y\)\(y\Rightarrow x\)
再过一个月就是 C 国成立的千年纪念日,所以 C 国的人民正在筹备盛大的游行庆典。目前 C 国得知接下来会有 \(q\) 次游行计划,第 \(i\) 次游行希望从城市 \(s_i\) 出发,经过若干个城市后,在城市 \(t_i\) 结束,且在游行过程中,一个城市可以被经过多次。为了增加游行的乐趣,每次游行还会临时修建出 \(k\)\(0 \le k \le 2\))条有向道路专门供本次游行使用,即其它游行计划不能通过本次游行修建的道路。
现在 C 国想知道,每次游行计划可能会经过多少座城市
注意:临时修建出的道路可以不满足 C 国道路原有的特点
\(n,q\leq 3\times 10^5,m\leq 6\times 10^5,0\leq k\leq 2\)

思路

\(O(Qn)\) 的做法就是对于每一个询问从 \(s\)\(t\) 分别跑一次 dfs(\(t\) 开始的那次需要建反图),对于图中一个点 \(x\),它可以对这次询问做贡献当且仅当在两次 dfs 中都到达了点 \(x\)

首先把这张图缩点,搞成一个 DAG。
题目中给了图的一个性质:对于三座城市 \(x\)\(y\)\(z\),若 \(x\Rightarrow z\)\(y\Rightarrow z\),那么有 \(x\Rightarrow y\)\(y\Rightarrow x\)。翻译一下就是说,必然存在一棵外向树,满足它的连通性和这张 DAG 相同。只需要跑一下拓扑排序,当点 \(u\) 走到点 \(v\) 的时候,\(v\) 剩余的入度为 \(0\) 了,那么就连边 \((u,v)\)
对于每次询问,相当于在树上有最多 \(6\) 个特殊点。把这几个点的虚树搞出来,虚树上一条边的权值就是原树上这条链所对应的所有强连通分量点的数量。为了方便后续计算,并不包括虚树上两个端点所对应的强连通分量。
把最多两条新建的边接上,然后对这 \(2+2k\) 个点跑 \(O(Qn)\) 的做法即可。
时间复杂度 \(O(n\log n+m+Qk)\)

代码

#include <bits/stdc++.h>
using namespace std;

const int N=300010,M=1200010,LG=19;
int n,m,Q,k,cnt,U[M],V[M],col[N],siz[N],a[N],X[3],Y[3];

int read()
{
	int d=0; char ch=getchar();
	while (!isdigit(ch)) ch=getchar();
	while (isdigit(ch)) d=(d<<3)+(d<<1)+ch-48,ch=getchar();
	return d;
}

struct edge
{
	int next,to,dis;
};

struct Graph2
{
	int tot,head[2][N];
	bool vis[2][N];
	edge e[2][M];
	
	void add(int from,int to,int dis)
	{
		e[0][++tot]=(edge){head[0][from],to,dis};
		head[0][from]=tot;
		swap(from,to);
		e[1][tot]=(edge){head[1][from],to,dis};
		head[1][from]=tot;
	}
	
	void dfs(int x,int id)
	{
		vis[id][x]=1;
		for (int i=head[id][x];~i;i=e[id][i].next)
		{
			int v=e[id][i].to;
			if (!vis[id][v]) dfs(v,id);
		}
	}
	
	void query(int s,int t)
	{
		int ans=0;
		for (int i=1;i<=cnt;i++)
		{
			if (!vis[0][a[i]] || !vis[1][a[i]]) continue;
			ans+=siz[a[i]];
			for (int j=head[0][a[i]];~j;j=e[0][j].next)
			{
				int v=e[0][j].to;
				if (vis[0][v] && vis[1][v]) ans+=e[0][j].dis;
			}
		}
		cout<<ans<<"\n";
		tot=0;
		for (int i=1;i<=cnt;i++)
		{
			head[0][a[i]]=head[1][a[i]]=-1;
			vis[0][a[i]]=vis[1][a[i]]=0;
		}
	}
}G2;

bool cmp(int x,int y);

struct Tree1
{
	int tot,rt,head[N],id[N],dep[N],dis[N],f[N][LG+1];
	edge e[M];
	
	void add(int from,int to)
	{
		e[++tot]=(edge){head[from],to,0};
		head[from]=tot;
	}
	
	void dfs(int x,int fa)
	{
		id[x]=++tot; dis[x]=dis[fa]+siz[x];
		dep[x]=dep[fa]+1; f[x][0]=fa;
		for (int i=1;i<=LG;i++)
			f[x][i]=f[f[x][i-1]][i-1];
		for (int i=head[x];~i;i=e[i].next)
			dfs(e[i].to,x);
	}
	
	int lca(int x,int y)
	{
		if (dep[x]<dep[y]) swap(x,y);
		for (int i=LG;i>=0;i--)
			if (dep[f[x][i]]>=dep[y]) x=f[x][i];
		if (x==y) return x;
		for (int i=LG;i>=0;i--)
			if (f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
		return f[x][0];
	}
	
	void build(int s,int t)
	{
		a[1]=s; a[2]=t;
		for (int i=1;i<=k;i++)
			a[i*2+1]=X[i],a[i*2+2]=Y[i];
		sort(a+1,a+3+2*k,cmp);
		cnt=unique(a+1,a+3+2*k)-a-1;
		stack<int> st; st.push(a[1]);
		int cnt1=cnt;
		for (int i=2;i<=cnt1;i++)
		{
			int p=lca(st.top(),a[i]);
			if (p!=st.top()) 
			{
				int x=st.top(); st.pop();
				for (;st.size() && dep[st.top()]>dep[p];st.pop())
					G2.add(st.top(),x,dis[x]-dis[st.top()]-siz[x]),x=st.top();
				G2.add(p,x,dis[x]-dis[p]-siz[x]);
				if (!st.size() || st.top()!=p)
					a[++cnt]=p,st.push(p);
			}
			st.push(a[i]);
		}
		while (st.size()>1)
		{
			int x=st.top(); st.pop();
			G2.add(st.top(),x,dis[x]-dis[st.top()]-siz[x]);
		}
		for (int i=1;i<=k;i++) G2.add(X[i],Y[i],0);
	}
}T1;

bool cmp(int x,int y)
{
	return T1.id[x]<T1.id[y];
}

struct Graph1
{
	int tot,head[N],dfn[N],low[N],deg[N];
	bool vis[N];
	edge e[M];
	stack<int> st;
	
	void add(int from,int to,bool flag=0)
	{
		e[++tot]=(edge){head[from],to,0};
		head[from]=tot;
		if (flag) deg[to]++;
	}
	
	void tarjan(int x)
	{
		dfn[x]=low[x]=++tot;
		st.push(x); vis[x]=1;
		for (int i=head[x];~i;i=e[i].next)
		{
			int v=e[i].to;
			if (!dfn[v])
				tarjan(v),low[x]=min(low[x],low[v]);
			else if (vis[v])
				low[x]=min(low[x],dfn[v]);
		}
		if (dfn[x]==low[x])
		{
			cnt++; siz[cnt]=1;
			for (;st.top()!=x;st.pop())
				col[st.top()]=cnt,vis[st.top()]=0,siz[cnt]++;
			col[x]=cnt; vis[x]=0; st.pop();
		}
	}
	
	void topsort()
	{
		queue<int> q;
		for (int i=1;i<=cnt;i++)
			if (!deg[i]) T1.rt=i,q.push(i);
		while (q.size())
		{
			int u=q.front(); q.pop();
			for (int i=head[u];~i;i=e[i].next)
			{
				int v=e[i].to;
				deg[v]--;
				if (!deg[v]) T1.add(u,v),q.push(v);
			}
		}
	}
}G1;

int main()
{
	memset(T1.head,-1,sizeof(T1.head));
	memset(G1.head,-1,sizeof(G1.head));
	memset(G2.head,-1,sizeof(G2.head));
	n=read(); m=read(); Q=read(); k=read();
	for (int i=1;i<=m;i++)
	{
		U[i]=read(); V[i]=read();
		G1.add(U[i],V[i]);
	}
	G1.tot=0;
	for (int i=1;i<=n;i++)
		if (!G1.dfn[i]) G1.tarjan(i);
	memset(G1.head,-1,sizeof(G1.head));
	G1.tot=0;
	for (int i=1;i<=m;i++)
		if (col[U[i]]!=col[V[i]])
			G1.add(col[U[i]],col[V[i]],1);
	G1.topsort();
	T1.tot=0; T1.dfs(T1.rt,0);
	while (Q--)
	{
		int s=col[read()],t=col[read()];
		for (int i=1;i<=k;i++)
			X[i]=col[read()],Y[i]=col[read()];
		T1.build(s,t);
		G2.dfs(s,0); G2.dfs(t,1);
		G2.query(s,t);
	}
	return 0;
}
posted @ 2021-07-26 22:28  stoorz  阅读(84)  评论(0编辑  收藏  举报