1. 程式人生 > 其它 >Codeforces 848D - Shake It!(DP)

Codeforces 848D - Shake It!(DP)

DP 模型轉化+字尾和優化 min 卷積

Codeforces 題面傳送門 & 洛谷題面傳送門

hot tea 一道。

首先我們考慮這個奇奇怪怪的最小割有什麼等價的表達。不難發現,如果我們選擇了 \(S\to T\) 這條邊並加入了一個新的節點 \(u\),那麼就會出現兩條邊 \(S\to u,u\to T\)。我們考慮把 \(S\)\(u\) 分別當作新的源點和匯點重複上面的過程,假設 \(S\to u\) 產生的流量為 \(f_1\),我們再把 \(u,T\) 也分別當作新的源匯併產生 \(f_2\) 的流量,那麼新產生的這個節點 \(u\) 對原圖的最小割,即最大流產生了 \(\min(f_1,f_2)\) 的貢獻。也就是說,當我們選擇 \(S\to T\)

這條邊並新添了一個點 \(u\),就等價於將原問題拆成了 \(S\to u\)\(u\to T\) 兩個子問題求解,這就天然地形成了 DP 的模型。

考慮 \(f_{n,m}\)​​ 表示對於一張初始只有 \(S,T\)​​ 兩個點和一條邊 \(S\to T\)​​ 的圖進行 \(n\)​​​ 次操作後能夠得到多少張最小割為 \(m\)​​ 的圖,再設 \(g_{n,m}\)​​ 表示對於一張初始有三個點 \(S,T,u\)​​ 和兩條邊 \(S\to u\)​​ 和 \(u\to T\)​​ 的圖在進行 \(n-1\)​​ 次操作後可以得到多少個最小割為 \(m\)​​ 的圖,轉移就考慮對 \(S\to u\)

​​ 長出的子圖和 \(u\to T\)​​ 長出的子圖分別進行了多少次操作,設為 \(k\)​ 和 \(n-1-k\)​,那麼轉移就是一個 \(\min\)​ 卷積的形式,即 \(g_{n,m}=\sum\limits_{k=0}^{n-1}\sum\limits_{\min(x,y)=m}f_{k,x}f_{n-1-k,y}\)​,眾所周知,\(\min\)​ 卷積可以通過處理字尾和做到線性,即假設 \(sf_{n,m}\)​ 為 \(f_{n,m}\)​ 的字尾和,\(sg_{n,m}\)​ 也同理,那麼 \(sg_{n,m}=\sum\limits_{k=0}^{n-1}sf_{k,m}sf_{n-1-k,m}\)
​,再一遍差分即可求出真正的 \(g\)。這樣我們就實現了 \(f\to g\)​。

接下來考慮怎樣 \(g\to f\),方便起見,我們將所有 \(S\to u,u\to T\) 進行 \(n-1\) 次操作得到的最小割為 \(m\) 的圖稱作一個“\((n,m)\) 結構”,將所有 \((n,m)\) 結構的總體稱作“\((n,m)\) 類”,那麼我們考慮一個揹包的思想,考慮所有 \((i,j)\) 類對 \(f_{n,m}\) 的貢獻,那麼我們列舉用了多少個 \((i,j)\) 類中的結構,設為 \(k\),那麼有轉移 \(f_{n,m}\leftarrow f_{n-ki,m-kj}·\dbinom{g_{i,j}+k-1}{k}\),其中後面那個組合數可以用隔板法來解釋,具體來說就是設 \((i,j)\) 類第 \(t\) 個結構出現了 \(x_t\) 次,那麼由於“經過置換得到的圖視為相同”這一條件的存在,一組 \((x_1,x_2,\cdots,x_{g_{i,j}})\) 就能唯一確定一張圖,方案數就是 \(x_1+x_2+\cdots+x_{g_{i,j}}=k\) 的解的個數,根據隔板法可知該值等於 \(\dbinom{g_{i,j}+k-1}{k}\)

還有一個小問題就是 DP 轉移的順序,如果我們不欽定 DP 轉移的順序就會算重。因此我們考慮從小到大列舉 \(i\) 再從小到大列舉 \(j\),算出 \(g_{i,j}\) 之後再用多重揹包的方式鬆弛所有 \(f_{n,m}\),不難發現這樣我們肯定會按照 \((i,j)\) 這樣的二元組的字典序順序進行多重揹包,也就不會擔心算重的問題了。這就有點類似於子集卷積那種“半線上”的感覺,學過子集卷積/半線上卷積的應該會比較好理解。

時間複雜度上界大概是 \(n^4\ln n\),因為後面列舉 \(k\) 那一維複雜度大概是調和級數的。

const int MAXN=50;
const int MOD=1e9+7;
int n,m,f[MAXN+5][MAXN+5],sf[MAXN+5][MAXN+5],g[MAXN+5][MAXN+5],sg[MAXN+5][MAXN+5];
int inv[MAXN+5];
int main(){
	scanf("%d%d",&n,&m);f[0][1]=sf[0][1]=1;
	for(int i=(inv[0]=inv[1]=1)+1;i<=MAXN;i++) inv[i]=1ll*inv[MOD%i]*(MOD-MOD/i)%MOD;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n+1;j++) for(int k=0;k<=i-1;k++)
			sg[i][j]=(sg[i][j]+1ll*sf[k][j]*sf[i-1-k][j])%MOD;
		for(int j=1;j<=n+1;j++) g[i][j]=(sg[i][j]-sg[i][j+1]+MOD)%MOD;
//		for(int j=1;j<=n+1;j++) printf("g %d %d %d\n",i,j,g[i][j]);
		for(int j=1;j<=n+1;j++){
			for(int k=n+1;k;k--) for(int l=n+1;l;l--){
				int mul=1;
				for(int t=1;t*i<=k&&t*j<=l;t++){
					mul=1ll*mul*(g[i][j]+t-1)%MOD*inv[t]%MOD;
					f[k][l]=(f[k][l]+1ll*f[k-t*i][l-t*j]*mul)%MOD;
				}
			}
		}
		for(int j=n+1;j;j--) sf[i][j]=(sf[i][j+1]+f[i][j])%MOD;
	} printf("%d\n",f[n][m]);
	return 0;
}