• 2021联合省选A卷题解


    Day 1

    T1 卡牌游戏

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

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

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

    接下来钦定最小值在后缀中,枚举 (r),那么此时我们一定要求 (a[l+1]gemin_{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
    ",ret);
    	return 0;
    }
    

    T2 矩阵游戏

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

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

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

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

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

    证明

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

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

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

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

    [egin{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('
    ');
    		else{
    			pc('Y');pc('E');pc('S');pc('
    ');
    			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('
    '),++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 ightarrow v)以及(v ightarrow u),满足路径上不经过编号(<v)的点。

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

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

    事实上,我们可以一遍 (floyd) 完成两件事,定义 (f[i][j]),当 (i>j) 是表示从 (i) 出发到 (j),满足路上所有的点编号 (>j) 时边编号的最小值,当 (ile 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],'
    ');
    	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
    ",ans);
    	return 0;
    }
    

    T3 支配

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

    首先,点 (u) 的受支配集大小一定不会增大,只可能减小,并且减小当且仅当存在一条从 (y)(u) 的路径(设增加的边是(x ightarrow 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,'
    ');
    	}
    	flush();
    	return 0; 
    }
    
  • 相关阅读:
    布局-float-margin-padding
    乡镇投票笔记
    Ajax实战
    Form,tagName和nodeName的区别
    Form,选择并转移导航菜单
    如何在ajax请求中设置特殊的RequestHeader
    nginx配置转发详解
    利用循环遍历的方式判断某个对象是否属于这个数组
    几个原生js方法总结
    chrome JS关闭当前页无效问题
  • 原文地址:https://www.cnblogs.com/tqxboomzero/p/14654041.html
Copyright © 2020-2023  润新知