zoukankan      html  css  js  c++  java
  • CF1402C 「CEOI2020」Star Trek

    CF1402C 「CEOI2020」Star Trek

    题目链接

    约定:假设从第(i)棵树的节点(a),连向了第(i+1)棵树的节点(b),则我们认为第(i+1)棵树是以(b)为根的。

    考虑一个DP。设(dp[i])表示依次连接了(i)棵树,【从第一棵树的根节点出发,先手必胜】的方案数。注意,第一棵树的根节点可以是任意一个节点(只有在求答案时,才强制要求第一棵树的根节点是(1),我们在定义状态时不考虑这个)。也就是说,(dp[i]),是把第一棵树根节点为(1dots n)的方案数加起来。

    转移时考虑在前面添加一棵树。

    朴素的转移,我们枚举添加的这棵树的根( ext{root})。此时需要做一些分类讨论:

    • 单独考虑一棵树,以( ext{root})为根时,是否是先手必胜的。如果是先手必胜的,此时我们要求连出去的边不能改变根节点( ext{root})的胜负状态。具体来说,枚举树里每一个节点(u),考虑从(u)连出去:
      • 如果改变(u)的胜负性后,不会影响到根节点( ext{root})的胜负性。那么后面(i-1)棵树的连边方案可以任意。方案数是(n imes n^{2(i-1)})。其中(n)表示给后面的第一棵树定一个根节点,(n^{2(i-1)})表示其他树之间任意连边。
      • 如果改变(u)的胜负性后,会影响到根节点( ext{root})的胜负性:
        • 如果(u)的子树是先手必胜的。那么随便连一条边,当走到(u)时,先手无论如何不会走向这条边。所以也可以任意连边,方案数是(n imes n^{2(i-1)})
        • 如果(u)的子树是先手必败的。则我们连出去的边必须必胜。因为如果连向了一个必败状态,则走到(u)时,先手就一定就会走我们新连的这条边,于是就改变了(u)的胜负性,进而改变了根的胜负性。因此,连边只能连向必胜,方案数是(dp[i-1])
    • 单独考虑一棵树,以( ext{root})为根时,如果不是先手必胜(也就是后手必胜),此时我们要求连出去的边必须改变根节点的胜负状态。具体来说,枚举树里每一个节点(u),考虑从(u)连出去:
      • 如果改变(u)的胜负性后,不会影响到根节点( ext{root})的胜负性。那一定不符合我们的要求。所以从(u)节点连出去的方案数是(0)
      • 如果改变(u)的胜负性后,会影响到根节点( ext{root})的胜负性:
        • 如果(u)的子树是先手必胜的。那么我们无论怎么连边,走到(u)时先手都不会走我们连出去的边。所以不可能改变(u)的胜负性,进而不可能改变根的胜负性。所以方案数还是(0)
        • 如果(u)的子树是先手必败的。那么我们连向一个必败点,就能改变(u)的胜负性。所以连边方案数是(n imes n^{2(i-1)}-dp[i-1])

    通过上述的分类讨论,我们发现,确定根后,只需要知道每个节点(u)的两个信息:

    • (u)的子树,是否是先手必胜的。
    • (u)胜负性改变,是否会影响根的胜负性。

    具体来说,只需要知道,对每个根来说,两种信息(2 imes2)(4)类点的数量分别是多少,就能计算出DP的转移系数。然后用矩阵快速幂优化DP。

    首先,可以枚举根,各做一次树形DP,求出这两个信息。时间复杂度(O(n^2))。具体来说:

    • (u)的子树是否先手必胜。这个只需要看儿子里,有没有先手必败的点即可。只要存在先手必败的儿子,则点(u)就是先手必胜的,否则是先手必败的。
    • (u)的胜负性改变,能否影响到根。这个要满足两个条件:
      • 首先,点(u)的父亲,必须能影响根。
      • 其次,点(u)的所有兄弟里(它父亲除了(u)以外的儿子),必须没有必败点。也就是说,要么点(u)父亲的儿子里压根没有必败点;要么恰好有一个必败点且这个点就是(u)

    这两个DP都比较简单。假设求出的两个信息,分别为(f_{ ext{root}}(u),g_{ ext{root}}(u))(f_{ ext{root}}(u),g_{ ext{root}}(u)in{0,1}))。那相当于对每个( ext{root}),求出一个:

    [ ext{cnt}[ ext{root}][xin{0,1}][yin{0,1}]=sum_{i=1}^{n}[f_{ ext{root}}(i)=x] imes[g_{ ext{root}}(i)=y] ]

    假设有了这个( ext{cnt})数组,我们就很容易求出系数,然后进行上述的DP并用矩阵快速幂优化了。

    先对(1),求出( ext{cnt}[1][x][y]),然后换根。换根的时候非常复杂,需要进行大量的讨论。具体来说,要预处理出一个(s(u))表示只考虑(以(1)为根时)点(u)的子树,子树里有多少(g_{1}(i)=1)的点。换句话说,就是不考虑(u)的父亲了,强制点(g(u)=1)。这个(s)在换根时,有很大的用处。

    其他细节,留给读者自行讨论。实在讨论不出来可以看看参考代码。不过看到这么长的代码,你估计宁愿自己讨论......

    时间复杂度(O(n+log d))

    参考代码:

    //problem:C
    //CEOI2020 D1T3
    #include <bits/stdc++.h>
    using namespace std;
    
    #define pb push_back
    #define mk make_pair
    #define lob lower_bound
    #define upb upper_bound
    #define fi first
    #define se second
    #define SZ(x) ((int)(x).size())
    
    typedef unsigned int uint;
    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair<int,int> pii;
    
    template<typename T>inline void ckmax(T& x,T y){x=(y>x?y:x);}
    template<typename T>inline void ckmin(T& x,T y){x=(y<x?y:x);}
    
    const int MAXN=1e5;
    const int MOD=1e9+7;
    inline int mod1(int x){return x<MOD?x:x-MOD;}
    inline int mod2(int x){return x<0?x+MOD:x;}
    inline void add(int& x,int y){x=mod1(x+y);}
    inline void sub(int& x,int y){x=mod2(x-y);}
    inline int pow_mod(int x,int i){int y=1;while(i){if(i&1)y=(ll)y*x%MOD;x=(ll)x*x%MOD;i>>=1;}return y;}
    
    int n;
    ll D;
    vector<int>G[MAXN+5];
    
    int root_f[MAXN+5],subt_f[MAXN+5],subt_cnt_0[MAXN+5],root_cnt_0[MAXN+5];
    void dfs1(int u,int fa){
    	subt_f[u]=0; // 先手必败
    	subt_cnt_0[u]=0;
    	for(int i=0;i<SZ(G[u]);++i){
    		int v=G[u][i];
    		if(v==fa) continue;
    		dfs1(v,u);
    		if(subt_f[v]==0){
    			subt_f[u]=1;
    			subt_cnt_0[u]++;
    		}
    	}
    }
    
    int root_aff[MAXN+5][2],subt_aff[MAXN+5][2];
    int sum[MAXN+5][2],sum_0[MAXN+5][2];
    void dfs2(int u,int fa){
    	root_aff[u][0]=root_aff[u][1]=0;
    	root_aff[u][subt_f[u]]=1;
    	for(int i=0;i<SZ(G[u]);++i){
    		int v=G[u][i];
    		if(v==fa) continue;
    		dfs2(v,u);
    		if(subt_cnt_0[u] - (subt_f[v]==0) == 0){
    			root_aff[u][0]+=root_aff[v][0];
    			root_aff[u][1]+=root_aff[v][1];
    		}
    		sum[u][0]+=subt_aff[v][0];
    		sum[u][1]+=subt_aff[v][1];
    		if(subt_f[v]==0){
    			sum_0[u][0]+=subt_aff[v][0];
    			sum_0[u][1]+=subt_aff[v][1];
    		}
    	}
    	subt_aff[u][0]=root_aff[u][0];
    	subt_aff[u][1]=root_aff[u][1];
    }
    
    int aff[MAXN+5];// 每个点的subt_f值改变,对根的f值是否有影响
    void dfs3(int u,int fa){
    	if(!aff[u]) return;
    	
    	if(subt_cnt_0[u]>1) return;
    	for(int i=0;i<SZ(G[u]);++i){
    		int v=G[u][i];
    		if(v==fa) continue;
    		if(subt_cnt_0[u] - (subt_f[v]==0) == 0){
    			aff[v]=1; // 除v以外都是0
    		}
    		else{
    			aff[v]=0;
    			continue;
    		}
    		dfs3(v,u);
    	}
    }
    
    int cnt[MAXN+5][2][2];
    int tmp[2],tmp_subt_cnt_aff_1[2];
    void dfs_changeRoot(int u,int fa,bool aff_clear){
    	if(root_cnt_0[fa] - (subt_f[u]==0) == 0){
    		aff[u]=1;
    	}
    	if(aff_clear){
    		aff[u]=0;
    	}
    	//cerr<<"---------------- "<<u<<" --------------------"<<endl;
    	int ff=0;
    	if(fa){
    		// 求以u为根时的胜负情况 root_f[u]
    		ff=root_f[fa];
    		if(root_cnt_0[fa]==1 && subt_f[u]==0){
    			assert(ff==1);
    			ff=0;
    		}
    		root_cnt_0[u]=subt_cnt_0[u]+(ff==0);
    		root_f[u]=(root_cnt_0[u]>0);
    		
    		// 以u为根时,重新求root_aff
    		sum[fa][0]-=subt_aff[u][0];
    		sum[fa][1]-=subt_aff[u][1];
    		if(subt_f[u]==0){
    			sum_0[fa][0]-=subt_aff[u][0];
    			sum_0[fa][1]-=subt_aff[u][1];
    		}
    		
    		tmp[0]=root_aff[fa][0];
    		tmp[1]=root_aff[fa][1];
    		if(root_cnt_0[fa] - (subt_f[u]==0) == 0){
    			tmp[0]-=root_aff[u][0];
    			tmp[1]-=root_aff[u][1];
    		}
    		tmp[root_f[fa]]--;
    		tmp[ff]++;
    		if(subt_f[u]==0){
    			if(root_cnt_0[fa]==1){
    				tmp[0]+=sum[fa][0];
    				tmp[1]+=sum[fa][1];
    			}
    			if(root_cnt_0[fa]==2){
    				tmp[0]+=sum_0[fa][0];
    				tmp[1]+=sum_0[fa][1];
    			}
    		}
    		
    		root_aff[u][0]=root_aff[u][1]=0;
    		root_aff[u][root_f[u]]=1;
    		if(root_cnt_0[u] - (ff==0) == 0){
    			root_aff[u][0]+=tmp[0];
    			root_aff[u][1]+=tmp[1];
    		}
    		for(int i=0;i<SZ(G[u]);++i){
    			int v=G[u][i];
    			if(v==fa) continue;
    			if(root_cnt_0[u] - (subt_f[v]==0) == 0){
    				root_aff[u][0]+=root_aff[v][0];
    				root_aff[u][1]+=root_aff[v][1];
    			}
    		}
    		
    		// 求以u为根时,整棵树里aff和f为0/1的数量 cnt[u][0/1][0/1]
    		// 先继承fa的
    		for(int i=0;i<=1;++i)for(int j=0;j<=1;++j)cnt[u][i][j]=cnt[fa][i][j];
    		
    		// 分三个部分
    		// 1. u节点本身
    		// 2. u原本的子树
    		// 3. fa变成u的儿子后新产生的子树
    		
    		// part1
    		cnt[u][aff[u]][subt_f[u]]--;
    		cnt[u][1][root_f[u]]++;
    		
    		// part2
    		int oaff=aff[u];
    		if(!aff[u]){
    			if(ff){
    				// fa变成儿子后,对u的子树无影响
    				// 但aff[u]变成了1后,子树会产生连锁反应
    				aff_clear=0;
    				aff[u]=1;
    				subt_aff[u][subt_f[u]]--;
    				cnt[u][0][0]-=subt_aff[u][0];
    				cnt[u][1][0]+=subt_aff[u][0];
    				cnt[u][0][1]-=subt_aff[u][1];
    				cnt[u][1][1]+=subt_aff[u][1];
    				subt_aff[u][subt_f[u]]++;
    			}
    			else{
    				// 受到fa的影响, u的子树里(不含u)仍然没有aff=1的点, 所以cnt不变
    				aff[u]=1;
    			}
    		}
    		else{
    			if(!ff){
    				// 受到fa的影响, u子树里一些原本aff=1的点(不含u), 现在全部变成aff=0
    				aff_clear=1;
    				subt_aff[u][subt_f[u]]--;
    				cnt[u][1][0]-=subt_aff[u][0];
    				cnt[u][0][0]+=subt_aff[u][0];
    				cnt[u][1][1]-=subt_aff[u][1];
    				cnt[u][0][1]+=subt_aff[u][1];
    				subt_aff[u][subt_f[u]]++;
    			}
    		}
    		// part3
    		assert(aff[fa]==1);
    		if(subt_cnt_0[u]==0){
    			// fa变成儿子后,aff还是1
    			cnt[u][1][root_f[fa]]--;
    			cnt[u][1][ff]++;
    			if(subt_f[u]==0){
    				// fa的其它儿子(u原来的兄弟,aff从0变成1)
    				if(root_cnt_0[fa]==1){
    					cnt[u][0][0]-=sum[fa][0];
    					cnt[u][1][0]+=sum[fa][0];
    					cnt[u][0][1]-=sum[fa][1];
    					cnt[u][1][1]+=sum[fa][1];
    				}
    				if(root_cnt_0[fa]==2){
    					cnt[u][0][0]-=sum_0[fa][0];
    					cnt[u][1][0]+=sum_0[fa][0];
    					cnt[u][0][1]-=sum_0[fa][1];
    					cnt[u][1][1]+=sum_0[fa][1];
    				}
    			}
    		}
    		else{
    			// fa变成儿子后,aff变成0了
    			cnt[u][1][root_f[fa]]--;
    			cnt[u][0][ff]++;
    			
    			cnt[fa][1][root_f[fa]]--;
    			
    			cnt[fa][1][0]-=oaff*subt_aff[u][0];
    			cnt[fa][1][1]-=oaff*subt_aff[u][1];
    			
    			cnt[u][1][0]-=cnt[fa][1][0];
    			cnt[u][0][0]+=cnt[fa][1][0];
    			cnt[u][1][1]-=cnt[fa][1][1];
    			cnt[u][0][1]+=cnt[fa][1][1];
    			
    			cnt[fa][1][0]+=oaff*subt_aff[u][0];
    			cnt[fa][1][1]+=oaff*subt_aff[u][1];
    			
    			cnt[fa][1][root_f[fa]]++;
    		}
    	}
    	//for(int i=0;i<=1;++i)for(int j=0;j<=1;++j)cerr<<"aff "<<i<<" f "<<j<<": "<<cnt[u][i][j]<<endl;
    	int x=subt_aff[fa][0],y=subt_aff[fa][1],z=subt_f[fa];
    	if(fa){
    		subt_aff[fa][0]=tmp[0],subt_aff[fa][1]=tmp[1],subt_f[fa]=ff;
    		sum[u][0]+=subt_aff[fa][0];
    		sum[u][1]+=subt_aff[fa][1];
    		if(ff==0){
    			sum_0[u][0]+=subt_aff[fa][0];
    			sum_0[u][1]+=subt_aff[fa][1];
    		}
    	}
    	for(int i=0;i<SZ(G[u]);++i){
    		int v=G[u][i];
    		if(v==fa) continue;
    		dfs_changeRoot(v,u,aff_clear);
    	}
    	if(fa){
    		subt_aff[fa][0]=x,subt_aff[fa][1]=y,subt_f[fa]=z;
    		sum[fa][0]+=subt_aff[u][0];
    		sum[fa][1]+=subt_aff[u][1];
    		if(subt_f[u]==0){
    			sum_0[fa][0]+=subt_aff[u][0];
    			sum_0[fa][1]+=subt_aff[u][1];
    		}
    	}
    }
    
    struct Matrix{
    	int a[2][2];
    	void identity(){a[0][0]=a[1][1]=1;a[0][1]=a[1][0]=0;}
    	Matrix(){a[0][0]=a[0][1]=a[1][0]=a[1][1]=0;}
    };
    Matrix operator*(const Matrix& X,const Matrix& Y){
    	Matrix Z;
    	for(int i=0;i<=1;++i)for(int j=0;j<=1;++j)for(int k=0;k<=1;++k)Z.a[i][j]=((ll)Z.a[i][j] + (ll)X.a[i][k]*Y.a[k][j])%MOD;
    	return Z;
    }
    Matrix mat_pow(Matrix X,ll i){
    	Matrix Y;Y.identity();
    	while(i) { if(i&1LL) Y=Y*X; X=X*X; i>>=1; }
    	return Y;
    }
    
    int main() {
    	cin>>n>>D;
    	for(int i=1;i<n;++i){
    		int u,v;
    		cin>>u>>v;
    		G[u].pb(v);
    		G[v].pb(u);
    	}
    	dfs1(1,0);
    	dfs2(1,0);
    	aff[1]=1;
    	dfs3(1,0);
    	//for(int i=1;i<=n;++i)cerr<<aff[i]<<" ";cerr<<endl;
    	for(int i=1;i<=n;++i){
    		cnt[1][aff[i]][subt_f[i]]++;
    	}
    	root_f[1]=subt_f[1];
    	root_cnt_0[1]=subt_cnt_0[1];
    	dfs_changeRoot(1,0,0);
    	
    	int trans_coef_f=0,trans_coef_n=0;
    	int ans_coef_f=0,ans_coef_n=0;
    	for(int i=1;i<=n;++i){
    		if(root_f[i]){
    			add(trans_coef_n,cnt[i][0][0]+cnt[i][0][1]+cnt[i][1][1]);
    			add(trans_coef_f,cnt[i][1][0]);
    		}
    		else{
    			add(trans_coef_n,cnt[i][1][0]);
    			sub(trans_coef_f,cnt[i][1][0]);
    		}
    		
    		if(i==1){
    			ans_coef_f=trans_coef_f;
    			ans_coef_n=trans_coef_n;
    		}
    	}
    	
    	/*
    	static int dp[MAXN+5];
    	dp[1]=0;
    	for(int i=1;i<=n;++i)if(root_f[i]==1)dp[1]++;
    	int v=n;
    	for(int i=2;i<=D;++i){
    		dp[i]=((ll)dp[i-1]*trans_coef_f%MOD + (ll)v*trans_coef_n%MOD)%MOD;
    		v=(ll)v*n%MOD*n%MOD;
    	}
    	
    	int ans=((ll)dp[D]*ans_coef_f%MOD + (ll)v*ans_coef_n%MOD)%MOD;
    	cout<<ans<<endl;
    	*/
    	
    	Matrix res;
    	res.a[0][0]=0;
    	for(int i=1;i<=n;++i)if(root_f[i]==1)res.a[0][0]++;
    	res.a[0][1]=n;
    	Matrix trans;
    	trans.a[0][0]=trans_coef_f;
    	trans.a[1][0]=trans_coef_n;
    	trans.a[1][1]=(ll)n*n%MOD;
    	res=res*mat_pow(trans,D-1);
    	int ans=((ll)res.a[0][0]*ans_coef_f%MOD + (ll)res.a[0][1]*ans_coef_n%MOD)%MOD;
    	cout<<ans<<endl;
    	return 0;
    }
    
  • 相关阅读:
    mysql处理字符串
    关于git新建本地分支与远程分支关联问题
    phpexcel相关函数
    centos添加开机启动项目
    centos搭建NFS网络文件系统
    centos 查看版本(转)
    ubuntu搭建nfs网络文件系统
    linux 日常学习
    从现在开始强迫自己使用 Reflect
    正则小括号实践
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/13581665.html
Copyright © 2011-2022 走看看