2021联合省选A卷题解

Day 1

T1 卡牌游戏

由于现在还没有官方数据,所以这是我考场上的思路,能通过随机数据,但不保证正确,欢迎大家来hack:

首先,容易发现最终答案一定会翻一段前缀 \(1\sim l\) 和一段后缀 \(r\sim n\),且如果将 \(a[l+1]\) 也算入前缀中,那么最小值一定会在前缀或后缀中。

我们首先钦定最小值在前缀中,枚举 \(l\),此时我们从后往前依次翻牌,尽量降低最大值,那么显然一旦在某一时刻后缀 \(b[r\sim n]\) 的最大值大于 \(a[r]\) 了,那么就不应该再翻 \(r\) 了,同时如果后缀最小值小于前缀最小值,我们也停止翻牌,这个临界点是容易二分出来的,当然双指针也是可以的。

接下来钦定最小值在后缀中,枚举 \(r\),那么此时我们一定要求 \(a[l+1]\ge\min_{i=r}^{n}b[i]\),同时对于所有符合这一条件的 \(l\),如果增加 \(l\) ,前缀最小值会减小导致可能无法满足钦定的条件,最大值都会增加导致答案增加,因此一定不优,我们只需要检验最小的符合条件的 \(l\) 即可,这是容易双指针/二分找出的。

upd:官方数据过了,uoj三十多组hack也过了

博主的憨批二分代码>
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int n,m,a[N],b[N];
int pmn[N],pmx[N],smn[N],smx[N];

int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i) scanf("%d",&a[i]);
	for(int i=1;i<=n;++i) scanf("%d",&b[i]);
	pmn[0]=smn[n+1]=a[n+1]=0x3f3f3f3f;
	pmx[0]=smx[n+1]=-0x3f3f3f3f;
	for(int i=1;i<=n;++i) pmn[i]=min(pmn[i-1],b[i]),pmx[i]=max(pmx[i-1],b[i]);
	for(int i=n;i>=1;--i) smn[i]=min(smn[i+1],b[i]),smx[i]=max(smx[i+1],b[i]);
	
	int ret=a[n]-a[1];
	for(int i=0;i<=m;++i){
		int l=i+n+1-m,r=n+1,ans=0,mn=min(pmn[i],a[i+1]);
		while(l<=r){
			int mid=(l+r)>>1;
			if(smn[mid]<mn||smx[mid]>a[mid]) l=mid+1;
			else r=mid-1,ans=mid;
		}
		ret=min(ret,max(pmx[i],max(a[ans-1],smx[ans]))-mn);
	}
	for(int i=n+1;n-i+1<=m;--i){
		int l=0,r=m-n+i-1,mn=min(smn[i],a[i-1]),mx=max(smx[i],a[i-1]);
		int y=upper_bound(a,a+n+2,mn)-a-1;
		if(y<=r&&pmn[y]>=mn) ret=min(ret,max(mx,pmx[y])-mn);
	}
	printf("%d\n",ret);
	return 0;
}

T2 矩阵游戏

首先考虑没有 \(0\le a_{i,j}\le 10^6\) 这一限制的情况,此时很容易构造一种合法情况:将第一行和第一列随机钦定一些值(博主全赋了 \(0\)),然后从左到右从上到下依次得到这个矩阵。

现在考虑通过一些调整来得到合法的矩阵,给出两种操作:

1.对于矩阵的一行,依次进行\(+1,-1,+1,-1,\dots\)的操作,显然这样做不会影响其对应的\(b\)矩阵。

2.对于矩阵的一列,作类似的操作。

引理:通过这两种操作,我们一定能得到所有可以达到的合法状态。

证明

通过一系列列操作修改第一行,再用第\(2\sim n\)行的行操作进行修改,一定能将第一行与第一列变成任何想要的情况。

而原问题相当于有\((n-1)(m-1)\)个方程,要解出\(nm\)个变量,那么还剩下\(n+m-1\)个可变量,上面的操作就等于是遍历了这些可变量的所有取值。

现在假设第\(i\)行进行了 \(c_i\) 次行操作,第 \(j\) 列进行了 \(d_j\) 次列操作,那么新的矩阵的每一位就变成了 \(0\le \pm c_i \pm d_j+a_{i,j}\le 10^6\),注意这里的 \(\pm\) 并不代表遍历所有情况,而仅代表每个 \(a_{i,j}\) 对应的符号。但是这样的不等式很像我们熟悉的差分约束,但是有的地方是和分约束,难以处理。

考虑将偶数行的 \(c\) 取反,奇数行的 \(d\) 取反,那么原矩阵就变成了:

\[\begin{bmatrix} c_1-d_1&d_2-c_1&c_1-d_3\\ d_1-c_2&c_2-d_2&d_3-c_2\\ c_3-d_1&d_2-c_3&c_3-d_3 \end{bmatrix} \]

于是我们就可以愉快的使用差分约束求解了,复杂度 \(\mathcal O(nm(n+m))\),由于\(SPFA\)卡不满,可以通过此题。

view code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1010;
const int MX=1e6;
int b[N][N];
int n,m,T,vis[N],ct[N],first[N],cnt;
ll dis[N],a[N][N];
struct node{
	int v,nxt;
	ll w;
}e[N*N];
inline void add(int u,int v,ll w){
	e[++cnt].v=v;e[cnt].w=w;e[cnt].nxt=first[u];first[u]=cnt;
}

namespace iobuff{
	const int LEN=1000000;
	char in[LEN+5],out[LEN+5];
	char *pin=in,*pout=out,*ed=in,*eout=out+LEN;
	inline char gc(void){
		#ifdef LOCAL
		return getchar();
		#endif
		return pin==ed&&(ed=(pin=in)+fread(in,1,LEN,stdin),ed==in)?EOF:*pin++;
	}
	inline void pc(char c){
		pout==eout&&(fwrite(out,1,LEN,stdout),pout=out);
		(*pout++)=c;
	}
	inline void flush(){fwrite(out,1,pout-out,stdout),pout=out;}
	template<typename T> inline void read(T &x){
		static int f;
		static char c;
		c=gc(),f=1,x=0;
		while(c<'0'||c>'9') f=(c=='-'?-1:1),c=gc();
		while(c>='0'&&c<='9') x=10*x+c-'0',c=gc();
		x*=f;
	}
	template<typename T> inline void putint(T x,char div){
		static char s[15];
		static int top;
		top=0;
		x<0?pc('-'),x=-x:0;
		while(x) s[top++]=x%10,x/=10;
		!top?pc('0'),0:0;
		while(top--) pc(s[top]+'0');
		pc(div);
	}
}
using namespace iobuff;

inline bool SPFA(){
	deque<int> q;
	for(int i=1;i<=n+m;++i) q.push_front(i),dis[i]=0;
	memset(vis+1,0,sizeof(int)*(n+m));
	memset(ct+1,0,sizeof(int)*(n+m));
	while(!q.empty()){
		int u=q.front();
		q.pop_front();vis[u]=0;
		for(int i=first[u];i;i=e[i].nxt){
			int v=e[i].v;
			if(dis[u]+e[i].w<dis[v]){
				dis[v]=dis[u]+e[i].w;
				if(!vis[v]){
					vis[v]=1;
					if(e[i].w<0) q.push_front(v);
					else q.push_back(v);
					if((++ct[v])>=n+m) return false;
				}
			}
		}
	}
	return true;
}

inline void init(){
	for(int i=1;i<n;++i)
		for(int j=1;j<m;++j)
			a[i+1][j+1]=b[i][j]-a[i][j]-a[i+1][j]-a[i][j+1];
	for(int i=1;i<=n;++i){
		for(int j=1;j<=m;++j){
			int l=-a[i][j],r=MX-a[i][j];
			if((i+j)%2==0){
				add(j+n,i,r);
				add(i,j+n,-l);
			}
			else{
				add(i,j+n,r);
				add(j+n,i,-l);
			}
		}
	}
}
int main(){
//	freopen("mat.in","r",stdin);
	read(T);
	while(T--){
		read(n);read(m);
		for(int i=1;i<n;++i) 
			for(int j=1;j<m;++j)
				read(b[i][j]);
		for(int i=1;i<=n;++i) a[1][i]=0;
		for(int i=1;i<=m;++i) a[i][1]=0;
		init();
		if(!SPFA()) pc('N'),pc('O'),pc('\n');
		else{
			pc('Y');pc('E');pc('S');pc('\n');
			for(int i=1;i<=n;++i){
				ll x=dis[i];
				if(i%2==0) x=-x;ll now=x;
				for(int j=1;j<=m;++j,now=-now) a[i][j]+=now;
			}
			for(int i=1;i<=m;++i){
				ll x=dis[i+n];
				if(i&1) x=-x;ll now=x;
				for(int j=1;j<=n;++j,now=-now) a[j][i]+=now; 
			}
			for(int i=1;i<=n;pc('\n'),++i)
				for(int j=1;j<=m;++j) putint(a[i][j],' ');
		}
		memset(first,0,sizeof(int)*(n+m+2));cnt=0;
	} 
	flush();
	return 0;
}

T3 图函数

考虑 \(h(G)\) 中,\(u,v\) 能作出贡献当且仅当存在两条路径\(u\rightarrow v\)以及\(v\rightarrow u\),满足路径上不经过编号\(<v\)的点。

于是我们不需要考虑每一张图,考虑每一个点对 \((u,v)\) 能作出贡献的一定满足所有合法路径 \(u\rightarrow v,v\rightarrow u\)上经过的边的编号最小值依然存在,因此可以求出每一组点对的编号最小值,进而求出答案的差分数组,后缀和得出答案。

如何求出编号最小值?,这是一个 \(floyd\) 的模型,我们只需要在正图反图上都从大到小遍历所有节点作为中间点,只考虑向 \(>\) 中间点编号的点转移,复杂度为 \(\mathcal O(n^3+m)\),但是它带有 \(2\) 倍的常数,因此难以通过。

事实上,我们可以一遍 \(floyd\) 完成两件事,定义 \(f[i][j]\),当 \(i>j\) 是表示从 \(i\) 出发到 \(j\),满足路上所有的点编号 \(>j\) 时边编号的最小值,当 \(i\le j\) 时表示从 \(i\) 出发到 \(j\),满足路上所有的点编号 \(>i\) 时边编号的最小值。然后同样从大到小枚举中间点转移即可。

view code
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int n,m;
int f[1010][1010],g[1010][1010],ret[N];
inline void floyd(){
	for(int k=n;k>=1;--k){
		for(int i=k+1;i<=n;++i) ret[min(f[i][k],f[k][i])]++;
		for(int i=1;i<=n;++i){
			if(f[i][k]){
				if(i>k){
					int now=f[i][k];
					for(int j=1;j<k;++j) 
						f[i][j]=max(f[i][j],min(now,f[k][j]));
				}
				else{
					int now=f[i][k];
					for(int j=1;j<=n;++j)
						f[i][j]=max(f[i][j],min(now,f[k][j]));
				}
			}
		}
	}	
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1,u,v;i<=m;++i){
		scanf("%d%d",&u,&v);
		f[u][v]=i;
	}
	floyd();
	for(int i=m;i>=1;--i) ret[i]+=ret[i+1];
	for(int i=0;i<=m;++i)
		printf("%d ",ret[i+1]+n);
	return 0;
}

Day 2

T1 宝石

首先将询问 \((u,v)\) 拆成 \((u,lca)\)\((lca,v)\) 两部分。

对于前一部分,我们可以预处理出从每个点向上走时下一类宝石的最近位置,称为其后继,找到该点的$ 2^i$ 级后继,倍增即可。

对于后一部分,考虑二分最终的答案\(x\),然后找到 \(v\) 的祖先中最近的\(x\),然后倍增跳 \(x\) 的前驱直至到达上行部分的答案处,再检验深度是否超过了 \(lca\) 即可,前驱的定义与后缀类似,可以用同样的方式预处理。

这一部分需要随时查询 \(x\) 的最近的颜色为 \(w\) 的点,我们可以可持久化,也可以将询问离线到 \(v\) 上,在处理所有询问时,实时更新当前点到祖先链上所有颜色的出现位置,就可以快速查询了。

复杂度为 \(\mathcal O(nlog^2(n))\)

view code
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int n,m,c,p[N],id[N],cnt;
struct node{
	int v,nxt;
}e[N<<1];
int first[N];
inline void add(int u,int v){e[++cnt].v=v;e[cnt].nxt=first[u];first[u]=cnt;}

namespace iobuff{
	const int LEN=1000000;
	char in[LEN+5],out[LEN+5];
	char *pin=in,*pout=out,*ed=in,*eout=out+LEN;
	inline char gc(void){
		#ifdef LOCAL
		return getchar();
		#endif
		return pin==ed&&(ed=(pin=in)+fread(in,1,LEN,stdin),ed==in)?EOF:*pin++;
	}
	inline void pc(char c){
		pout==eout&&(fwrite(out,1,LEN,stdout),pout=out);
		(*pout++)=c;
	}
	inline void flush(){fwrite(out,1,pout-out,stdout),pout=out;}
	template<typename T> inline void read(T &x){
		static int f;
		static char c;
		c=gc(),f=1,x=0;
		while(c<'0'||c>'9') f=(c=='-'?-1:1),c=gc();
		while(c>='0'&&c<='9') x=10*x+c-'0',c=gc();
		x*=f;
	}
	template<typename T> inline void putint(T x,char div){
		static char s[15];
		static int top;
		top=0;
		x<0?pc('-'),x=-x:0;
		while(x) s[top++]=x%10,x/=10;
		!top?pc('0'),0:0;
		while(top--) pc(s[top]+'0');
		pc(div);
	}
}
using namespace iobuff;

int pa[N][20],fa[N],dep[N],up[N][20],dwn[N][20],col[N],ret[N];
int MX,lim,st[N],w[N];
inline int LCA(int u,int v){
	if(dep[u]<dep[v]) swap(u,v);
	int t=dep[u]-dep[v];
	for(int i=MX;i>=0;--i) if(t&(1<<i)) u=pa[u][i];		
	if(u==v) return u;
	for(int i=MX;i>=0;--i)
		if(pa[u][i]!=pa[v][i]) u=pa[u][i],v=pa[v][i];
	return pa[u][0];
}


inline void dfs(int u,int f){
	fa[u]=f;dep[u]=dep[f]+1;
	int rec=col[w[u]];col[w[u]]=u;
	up[u][0]=col[w[u]+1];
	dwn[u][0]=w[u]==0?0:col[w[u]-1];
	pa[u][0]=f;st[u]=col[1];
	
	for(int i=1;(1<<i)<=dep[u];++i)
		pa[u][i]=pa[pa[u][i-1]][i-1];
	for(int i=1;(1<<i)<=c-w[u]+1;++i) up[u][i]=up[up[u][i-1]][i-1];
	for(int i=1;(1<<i)<=w[u];++i) dwn[u][i]=dwn[dwn[u][i-1]][i-1];
	for(int i=first[u];i;i=e[i].nxt){
		int v=e[i].v;if(v==f) continue;
		dfs(v,u);
	}
	
	col[w[u]]=rec;
}

inline bool check(int u,int lca,int x,int now){
	if(dep[col[x]]<dep[lca]) return false;
	u=col[x];
	int rem=x-now-1;
	for(int i=lim;i>=0;--i){
		if(rem&(1<<i)){
			if(dep[dwn[u][i]]<dep[lca]) return false;
			u=dwn[u][i];
		}
	}
	return true;
}

vector<pair<int,int> > que[N];
inline void solve(int u){
	int rec=col[w[u]];col[w[u]]=u;
	
	for(int i=0;i<que[u].size();++i){
		int v=que[u][i].first,id=que[u][i].second;
		int lca=LCA(u,v),now=0;
		if(dep[st[v]]>=dep[lca]){
			v=st[v];now=1;
			for(int i=lim;i>=0;--i)
				if(dep[up[v][i]]>=dep[lca]) v=up[v][i],now+=1<<i;
		}
		int l=now+1,r=c,ans=now;
		while(l<=r){
			int mid=(l+r)>>1;
			if(check(u,lca,mid,now)) l=mid+1,ans=mid;
			else r=mid-1;
		}
		ret[id]=ans;
	}
	
	for(int i=first[u];i;i=e[i].nxt){
		int v=e[i].v;if(v==fa[u]) continue;
		solve(v); 
	}
	col[w[u]]=rec;
}
int main(){
	read(n);read(m);read(c);
	MX=log2(n);lim=log2(c);
	for(int i=1;i<=c;++i) read(p[i]),id[p[i]]=i;
	for(int i=1;i<=n;++i) read(w[i]),w[i]=id[w[i]];
	for(int i=1,u,v;i<n;++i){
		read(u);read(v);
		add(u,v);add(v,u);
	}
	int q;read(q);
	for(int i=1;i<=q;++i){
		int u,v;read(u);read(v);
		que[v].push_back(make_pair(u,i));
	}
	dfs(1,0);
	solve(1);
	for(int i=1;i<=q;++i) putint(ret[i],'\n');
	flush();
	return 0;
}

T2 滚榜

考虑暴力怎么做,暴力是不是,枚举全排列,检验某一个排列时,对于每一支队伍都尽量选择最小的 \(b_i\) ,看总题数够不够。

考虑一个暴力的 \(dp\),定义 \(dp[s][u][w][m]\),表示当前已经公布了 \(s\) 对应的队伍集合的成绩,上一个公布的队伍为 \(u\),它的最终分数为 \(w\),现在还剩下 \(m\) 道题可以使用。然后暴力枚举每一维信息按照上面的贪心进行转移,复杂度为 \(\mathcal O(2^nn^2m^2)\),貌似比枚举全排列还要慢。

我们发现状态 \(w\) 是可以优化掉的,它之所以需要它是因为要为下一支队伍提供加分的下限,那么当我们为队伍 \(i\) 增加 \(b_i\) 的分数时,直接为所有还未公布队伍的成绩都先增加一个 \(b_i\),那么公布它们的成绩时就只需要比较 \(a\) 的大小了,增加 \(b_i\) 也不用直接加,直接在剩余做题数上减去若干个 \(b\),默认后面所有队伍的成绩已经加上了 \(b\) 就可以了。至此,复杂度优化到了 \(\mathcal O(2^nn^2m)\),可以通过此题。

view code
#include<bits/stdc++.h>
using namespace std; 
typedef long long ll;
const int N=20;
int n,m,a[N];
ll ans=0,dp[(1<<13)+20][14][510];
int main(){
	scanf("%d%d",&n,&m);
	int pos=1;
	for(int i=1;i<=n;++i){
		scanf("%d",&a[i]);
		if(a[i]>a[pos]) pos=i;
	}
	dp[0][pos][m]=1;
	for(int i=1;i<(1<<n);++i){
		int len=0;
		for(int j=1;j<=n;++j) if(i&(1<<j-1)) len++;
		for(int j=1;j<=n;++j){
			if(!(i&(1<<j-1))) continue;
			int s=i^((1<<j-1));
			for(int k=1;k<=n;++k){
				if(!(s&(1<<k-1)))
					if(s!=0||k!=pos) continue;
				int dis=max(j<=k?a[k]-a[j]:a[k]-a[j]+1,0),p=dis*(n-len+1);
				for(int ret=p;ret<=m;++ret) dp[i][j][ret-p]+=dp[s][k][ret];
			}
			if(i==(1<<n)-1)
				for(int k=0;k<=m;++k) ans+=dp[i][j][k];
		}
	}
	printf("%lld\n",ans);
	return 0;
}

T3 支配

我们首先建出原图的支配树,满足所有支配点 \(u\) 的点都是 \(u\) 在支配树上的祖先。\(\mathcal O(nm)\) 的做法是容易的,直接保留枚举每个点,将其断掉后,从 \(1\) 出发搜索即可,也有更快的 \(\mathcal O(nlog(n))\) 的做法,具体请参见这篇博客,博主的代码使用的也是这种方法。

首先,点 \(u\) 的受支配集大小一定不会增大,只可能减小,并且减小当且仅当存在一条从 \(y\)\(u\) 的路径(设增加的边是\(x\rightarrow y\)\(LCA(x,y)=lca\))不经过支配树 \(lca-u\) 链上的某一点 \(z\),因为此时点 \(z\) 就不再支配 \(u\)

这个结论的限制还是太宽泛了,给出一个更进一步的结论:点 \(u\) 的受支配集大小减少当且仅当存在一条从 \(y\)\(u\) 的路径不经过 \(lca\) 的祖先节点或其祖先的儿子。

对于这一结论的证明。首先,存在这条路径时,它不会经过 \(lca-u\) 链上 \(lca\) 的直接儿子(这是一定存在的),因此必要性得证。

对于充分性,考虑存在一条从 \(y\)\(u\) 的路径经过了 \(lca\) 某个祖先但没有经过 \(lca-u\) 链上的所有点,那么此时该点到 \(u\) 的所有点都是必经的,否则不符合支配树的定义。如果经过了某个祖先的儿子,那么一定会必经 \(lca-u\) 链上 \(lca\) 的直接儿子,接下来同样必须要经过 \(lca-u\) 链上的所有点。

于是对于每个询问,\(\mathcal O(n)\)标记后直接 \(bfs\) 即可。

view code
#include<bits/stdc++.h>
using namespace std;
namespace iobuff{
	const int LEN=1000000;
	char in[LEN+5],out[LEN+5];
	char *pin=in,*pout=out,*ed=in,*eout=out+LEN;
	inline char gc(void){
		#ifdef LOCAL
		return getchar();
		#endif
		return pin==ed&&(ed=(pin=in)+fread(in,1,LEN,stdin),ed==in)?EOF:*pin++;
	}
	inline void pc(char c){
		pout==eout&&(fwrite(out,1,LEN,stdout),pout=out);
		(*pout++)=c;
	}
	inline void flush(){fwrite(out,1,pout-out,stdout),pout=out;}
	template<typename T> inline void read(T &x){
		static int f;
		static char c;
		c=gc(),f=1,x=0;
		while(c<'0'||c>'9') f=(c=='-'?-1:1),c=gc();
		while(c>='0'&&c<='9') x=10*x+c-'0',c=gc();
		x*=f;
	}
	template<typename T> inline void putint(T x,char div){
		static char s[15];
		static int top;
		top=0;
		x<0?pc('-'),x=-x:0;
		while(x) s[top++]=x%10,x/=10;
		!top?pc('0'),0:0;
		while(top--) pc(s[top]+'0');
		pc(div);
	}
}
using namespace iobuff;

const int N=3e5+10;
int n,m,tot;
int dfn[N],pos[N],mi[N],fa[N],f[N];
int semi[N],idom[N];
struct node{
	int v,nxt;
};
struct graph{
	int first[N],cnt;
	node e[N];
	inline void add(int u,int v){e[++cnt].v=v;e[cnt].nxt=first[u];first[u]=cnt;}
	inline void init(){cnt=0;memset(first+1,0,sizeof(int)*(n));}
}g,rg,ng,tr;
inline void dfs(int u){
	dfn[u]=++tot;pos[tot]=u;
	for(int i=g.first[u];i;i=g.e[i].nxt){
		int v=g.e[i].v;
		if(!dfn[v]) fa[v]=u,dfs(v);
	}
}
inline int find(int x){
	if(f[x]==x) return x;
	int t=f[x];f[x]=find(f[x]);
	if(dfn[semi[mi[t]]]<dfn[semi[mi[x]]]) mi[x]=mi[t];
	return f[x];
} 
inline void findidom(){
	for(int i=1;i<=n;++i) mi[i]=semi[i]=f[i]=i;
	for(int i=n;i>=2;--i){
		int u=pos[i],sem=n;
		for(int j=rg.first[u];j;j=rg.e[j].nxt){
			int v=rg.e[j].v;
			if(dfn[v]<dfn[u]) sem=min(sem,dfn[v]);
			else find(v),sem=min(sem,dfn[semi[mi[v]]]); 
		}
		semi[u]=pos[sem];f[u]=fa[u];
		ng.add(semi[u],u); 
		
		u=pos[i-1];
		for(int j=ng.first[u];j;j=ng.e[j].nxt){
			int v=ng.e[j].v;
			find(v);
			if(semi[mi[v]]==u) idom[v]=u;
			else idom[v]=mi[v];
		}
	}
	for(int i=2;i<=n;++i){
		int u=pos[i];
		if(idom[u]!=semi[u]) idom[u]=idom[idom[u]];
		tr.add(idom[u],u);
	}
}
int pa[N][20],dep[N],MX,q;
inline void dfs_tr(int u){
	for(int i=1;(1<<i)<=dep[u];++i) pa[u][i]=pa[pa[u][i-1]][i-1];
	for(int i=tr.first[u];i;i=tr.e[i].nxt){
		int v=tr.e[i].v;
		pa[v][0]=u;dep[v]=dep[u]+1;dfs_tr(v);
	}
}
inline int LCA(int u,int v){
	if(dep[u]<dep[v]) swap(u,v);
	int t=dep[u]-dep[v];
	for(int i=MX;i>=0;--i) if(t&(1<<i)) u=pa[u][i];		
	if(u==v) return u;
	for(int i=MX;i>=0;--i)
		if(pa[u][i]!=pa[v][i]) u=pa[u][i],v=pa[v][i];
	return pa[u][0];
}
int vis[N],pd[N];

int main(){
//	freopen("dominator1.in","r",stdin);
	read(n);read(m);read(q);
	for(int i=1,u,v;i<=m;++i){
		read(u);read(v);
		g.add(u,v);rg.add(v,u);
	}
	MX=log2(n);
	dfs(1);
	findidom();
	dep[1]=0;dfs_tr(1);
	while(q--){
		int u,v;read(u);read(v);
		int lca=LCA(u,v);
		memset(pd+1,0,sizeof(int)*(n));
		memset(vis+1,0,sizeof(int)*(n));
		int now=lca;
		while(now){
			pd[now]=1;
			for(int i=tr.first[now];i;i=tr.e[i].nxt){
				int v=tr.e[i].v;
				pd[v]=1;
			}
			now=pa[now][0];
		}
		queue<int> q;q.push(v);
		int ans=0;
		while(!q.empty()){
			int u=q.front();q.pop();
			if(pd[u]) continue;vis[u]=1;
			ans++;
			for(int i=g.first[u];i;i=g.e[i].nxt){
				int v=g.e[i].v;
				if(!vis[v]&&!pd[v]) vis[v]=1,q.push(v);
			}
		}
		putint(ans,'\n');
	}
	flush();
	return 0; 
}
posted @ 2021-04-13 16:51  cjTQX  阅读(199)  评论(0编辑  收藏  举报