題解 洛谷P6478 [NOI Online #2 提高組] 游戲


題目鏈接

考慮求出一個數組\(g\)\(g[i]\)表示至少\(i\)個非平局的方案數。也就是說,我們欽定了\(i\)對點,每對點都是“祖先-后代”的關系,剩下的\(m-i\)對點可以任意匹配,此時的方案數就是\(g[i]\)。我們設答案為\(f[0\dots m]\)\(f[i]\)表示的是恰好\(i\)個非平局回合的方案數。\(f\)\(g\)的關系,可不是一個簡單的后綴和那么簡單。假設我們欽定了\(i\)個非平局回合,而我們當前的方案里實際上有\(j\)個非平局回合(\(j>i\)),則\(f[j]\)會被算在\(g[i]\)\(j\choose i\)次。用公式表達就是:

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

如果已經求出了\(g\)數組,如何計算出\(f\)呢?這就是經典的二項式反演,其結論是:

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

根據其定義,可以直接\(O(n^2)\)求出整個\(f\)數組。該復雜度對於本題而言已經足夠。

考慮如何求出\(g\)數組。

講一個我在考場上寫的暴力。想看正解的讀者可以跳過本段。做狀壓DP。設\(dp[i][mask]\)表示考慮了小A的前\(i\)個節點,已經匹配了小B的\(mask\)這些節點(匹配是指欽定一對為“祖先-后代”關系的點),這種情況的方案數。判斷兩個節點是否為“祖先-后代”關系,可以用dfs序做到\(O(1)\)。最后,\(g[i]=\sum_{\text{cnt}(mask)=i}dp[m][mask]\cdot(m-i)!\),其中\(\text{cnt}({mask})\)表示\(mask\)中二進制為\(1\)位數,\((m-i)!\)是剩余節點任意匹配的方案數。該算法時間復雜度\(O(2^mm)\)。不足以通過本題。

正解是根據“祖先-后代”關系這個要求,做樹形DP。設\(dp[u][i]\)表示在節點\(u\)的子樹內,欽定了\(i\)對有“祖先-后代”關系的點,選出這些點的方案數。則\(g[i]=dp[root][i]\cdot (m-i)!\)

如何轉移?轉移時,我們既可以把\(u\)的兒子\(v\)子樹中的匹配直接裝入背包,也可以用\(u\)節點本身去和子樹內的點創造一對新增的匹配。這就涉及到一個問題:節點\(u\)是否已經被使用過。在轉移時,可以定義一個數組\(cur[u][i][t\in\{0,1\}]\),前面兩維定義和\(dp\)數組相同,第三維\(t\in\{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]<<" \n"[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;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM