zoukankan      html  css  js  c++  java
  • 题解 洛谷P6478 [NOI Online #2 提高组] 游戏

    题目链接

    考虑求出一个数组(g)(g[i])表示至少(i)个非平局的方案数。也就是说,我们钦定了(i)对点,每对点都是“祖先-后代”的关系,剩下的(m-i)对点可以任意匹配,此时的方案数就是(g[i])。我们设答案为(f[0dots m])(f[i])表示的是恰好(i)个非平局回合的方案数。(f)(g)的关系,可不是一个简单的后缀和那么简单。假设我们钦定了(i)个非平局回合,而我们当前的方案里实际上有(j)个非平局回合((j>i)),则(f[j])会被算在(g[i])(jchoose i)次。用公式表达就是:

    [g[i]=sum_{j=i}^{m}{jchoose i}f[j] ]

    如果已经求出了(g)数组,如何计算出(f)呢?这就是经典的二项式反演,其结论是:

    [f[i]=sum_{j=i}^{n}(-1)^{j-i}{jchoose i}g[j] ]

    根据其定义,可以直接(O(n^2))求出整个(f)数组。该复杂度对于本题而言已经足够。

    考虑如何求出(g)数组。

    讲一个我在考场上写的暴力。想看正解的读者可以跳过本段。做状压DP。设(dp[i][mask])表示考虑了小A的前(i)个节点,已经匹配了小B的(mask)这些节点(匹配是指钦定一对为“祖先-后代”关系的点),这种情况的方案数。判断两个节点是否为“祖先-后代”关系,可以用dfs序做到(O(1))。最后,(g[i]=sum_{ ext{cnt}(mask)=i}dp[m][mask]cdot(m-i)!),其中( ext{cnt}({mask}))表示(mask)中二进制为(1)位数,((m-i)!)是剩余节点任意匹配的方案数。该算法时间复杂度(O(2^mm))。不足以通过本题。

    正解是根据“祖先-后代”关系这个要求,做树形DP。设(dp[u][i])表示在节点(u)的子树内,钦定了(i)对有“祖先-后代”关系的点,选出这些点的方案数。则(g[i]=dp[u][i]cdot (m-i)!)

    如何转移?转移时,我们既可以把(u)的儿子(v)子树中的匹配直接装入背包,也可以用(u)节点本身去和子树内的点创造一对新增的匹配。这就涉及到一个问题:节点(u)是否已经被使用过。在转移时,可以定义一个数组(cur[u][i][tin{0,1}]),前面两维定义和(dp)数组相同,第三维(tin{0,1})表示节点(u)是否已经被使用过。全部转移完后,令(dp[u][i]=cur[u][i][0]+cur[u][i][1])即可。

    时间复杂度(O(n^2))

    参考代码:

    //problem:P6478
    #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;
    
    const int MAXN=5000,MAXM=2500,MOD=998244353;
    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,m,fac[MAXN+5],ifac[MAXN+5];
    inline int comb(int n,int k){
    	if(n<k)return 0;
    	return (ll)fac[n]*ifac[k]%MOD*ifac[n-k]%MOD;
    }
    
    char s[MAXN+5];
    
    struct EDGE{int nxt,to;}edge[MAXN*2+5];
    int head[MAXN+5],tot;
    inline void add_edge(int u,int v){edge[++tot].nxt=head[u],edge[tot].to=v,head[u]=tot;}
    
    int sz[MAXN+5],cnt0[MAXN+5],cnt1[MAXN+5],dp[MAXN+5][MAXM+5],cur[MAXN+5][MAXM+5][2],tmp[MAXM+5][2],g[MAXM+5];
    void dfs(int u,int fa){
    	cur[u][0][0]=1;
    	sz[u]=1;
    	cnt0[u]=(s[u]=='0');
    	cnt1[u]=(s[u]=='1');
    	for(int i=head[u];i;i=edge[i].nxt){
    		int v=edge[i].to;
    		if(v==fa)continue;
    		dfs(v,u);
    		int su=min(sz[u]/2,min(cnt0[u],cnt1[u])),sv=min(sz[v]/2,min(cnt0[v],cnt1[v])),stot=(sz[u]+sz[v])/2;
    		for(int j=0;j<=stot;++j)tmp[j][0]=tmp[j][1]=0;
    		for(int j=0;j<=su;++j){
    			for(int k=0;k<=sv;++k){
    				add(tmp[j+k][0],(ll)cur[u][j][0]*dp[v][k]%MOD);
    				add(tmp[j+k][1],(ll)cur[u][j][1]*dp[v][k]%MOD);
    				int free0=cnt0[v]-k,free1=cnt1[v]-k;
    				if(j+k<stot){
    					if(s[u]=='0'&&free1){
    						add(tmp[j+k+1][1],(ll)cur[u][j][0]*dp[v][k]%MOD*free1%MOD);
    					}
    					if(s[u]=='1'&&free0){
    						add(tmp[j+k+1][1],(ll)cur[u][j][0]*dp[v][k]%MOD*free0%MOD);
    					}
    				}
    			}
    		}
    		for(int j=0;j<=stot;++j)cur[u][j][0]=tmp[j][0],cur[u][j][1]=tmp[j][1];
    		
    		sz[u]+=sz[v];
    		cnt0[u]+=cnt0[v];
    		cnt1[u]+=cnt1[v];
    	}
    	for(int i=0;i<=sz[u]/2;++i)dp[u][i]=mod1(cur[u][i][0]+cur[u][i][1]);
    }
    int main() {
    	fac[0]=1;ifac[0]=1;
    	for(int i=1;i<=MAXN;++i)fac[i]=(ll)fac[i-1]*i%MOD,ifac[i]=pow_mod(fac[i],MOD-2);
    	
    	cin>>n;m=n/2;
    	cin>>(s+1);
    	for(int i=1,u,v;i<n;++i)cin>>u>>v,add_edge(u,v),add_edge(v,u);
    	dfs(1,0);
    	//for(int i=0;i<=m;++i)cout<<dp[1][i]<<" 
    "[i==m];
    	for(int i=0;i<=m;++i)g[i]=(ll)dp[1][i]*fac[m-i]%MOD;
    	for(int i=0;i<=m;++i){
    		int ans=0;
    		for(int j=i;j<=m;++j){
    			if((j-i)&1)sub(ans,(ll)comb(j,i)*g[j]%MOD);
    			else add(ans,(ll)comb(j,i)*g[j]%MOD);
    		}
    		//ans=(ll)ans*ifac[m]%MOD;
    		cout<<ans<<endl;
    	}
    	return 0;
    }
    
  • 相关阅读:
    深入理解JVM(2)——揭开HotSpot对象创建的奥秘
    深入理解JVM(3)——垃圾收集策略详解
    深入理解JVM(4)——对象内存的分配策略
    深入理解JVM(1)——JVM内存模型
    学习 IOC 设计模式前必读:依赖注入的三种实现
    学习Struts--Chap07:Struts2文件上传和下载
    POJ2823_Sliding Window
    POJ3378_Crazy Thairs
    POJ2374_Fence Obstacle Course
    POJ3709_K-Anonymous Sequence
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/12776183.html
Copyright © 2011-2022 走看看