Codeforces 1326F Wise Men (容斥原理、状压 DP、子集和变换、划分数)
題目鏈接
F1: https://codeforces.com/contest/1326/problem/F1
F2: https://codeforces.com/contest/1326/problem/F2
題解
好題。
考慮容斥,對每個 01 串求滿足串中為 \(1\) 的位置必須為 \(1\)、串中為 \(0\) 的位置 \(0\) 或 \(1\) 均可的排列的個數。最后把超集和還原回來即可。
這樣的好處是,本質不同的狀態只有拆分數 \(P(n)\) 個,即一個狀態的答案只和所有連續的 \(1\) 的長度構成的可重集合有關。于是考慮 DFS 枚舉劃分數。
先預處理 \(f_S\) 表示 \(S\) 點集內有多少條哈密爾頓回路經過的邊全是 \(1\).
對于一個狀態,假設每一段的長度是 \(a_1,a_2,...,a_l\). 那么就相當于我們要找 \(l\) 個狀態 \(s_1,s_2,...,s_l\), 滿足 \(\forall i, \text{bitcnt}(s_i)=a_i\) 且 \(\text{or}^l_{i=1}s_i=2^n-1\),貢獻為 \(\prod^l_{i=1}f_{s_i}\). 寫成集合冪級數的形式,設 \(g_i\) 是一個集合冪級數,滿足有且僅有在 \(\text{bitcnt}(s)=i\) 的位置有值,值為 \(f_s\),則這個狀態的總方案數等于 \(g_{a_1},g_{a_2},...,g_{a_l}\) 的子集卷積。于是可以直接使用子集卷積計算,可以做到 \(O(2^nP(n)n^2)\) 左右的復雜度。但是還是不行。
注意到 \(\sum^l_{i=1}a_i=n\),且 \(g_{a_i}\) 僅僅在 \(\text{bitcnt}(a_i)\) 處有值。也就是說我們其實根本不需要使用子集卷積——子集卷積的方法是給每個集合冪級數增加一維長度的限制,但是這里我們已經對長度進行了限制!假設有任何兩個 \(s_i\) 有交,那么所有 \(s_i\) 的并的大小就不可能為 \(n\). 于是直接對 \(g_{a_i}\) 這些集合冪級數作 or 卷積即可。
枚舉劃分后,計算 or 卷積的時間復雜度為所有劃分方案的總長度,足以通過。但是我們可以邊 DFS 邊維護 or 卷積,復雜度變成了搜索樹的節點個數乘以 \(2^n\).
劃分數搜索時,比較好的方法是從大到小搜索,每次放的數不超過上次放的。這時只要上次放的數不小于 \(2\),每個節點分叉數就一定大于 \(1\). 假設剩下一堆 \(1\) 是一起放的,那么節點數顯然為 \(O(P(n))\);否則據 EI 爺說是 \(O(P(n)\sqrt n)\) 的。這里可以預處理 \(g_1\) 的冪來做到前者的復雜度。
總時間復雜度 \(O(2^n(n^2+T(n))\),其中 \(T(n)=O(P(n))\) 或 \(O(P(n)\sqrt n)\) 或 \(O(\text{sum of length of all partitions})\).
代碼
#include<bits/stdc++.h> #define llong long long #define mkpr make_pair #define x first #define y second #define iter iterator #define riter reversed_iterator #define y1 Lorem_ipsum_dolor using namespace std;inline int read() {int x = 0,f = 1; char ch = getchar();for(;!isdigit(ch);ch=getchar()) {if(ch=='-') f = -1;}for(; isdigit(ch);ch=getchar()) {x = x*10+ch-48;}return x*f; }const int mxN = 18; int bitcnt[(1<<mxN)+3]; llong f[mxN+3][(1<<mxN)+3]; llong dp[(1<<mxN)+3][mxN+3]; llong g[mxN+3][(1<<mxN)+3]; int part[mxN+3],aux[mxN+3]; llong h[(1<<mxN)+3]; char a[mxN+3][mxN+3]; int n,m;void dfs(int rst,int lst) {if(rst==0){llong ret = 0ll;for(int i=0; i<(1<<n); i++){ret += ((n-bitcnt[i])&1)?-g[m][i]:g[m][i];} // printf("("); for(int i=1; i<=m; i++) printf("%d ",part[i]); printf("): %I64d\n",ret);for(int i=1; i<=m; i++) {aux[i] = part[m-i+1];}do{int pos = 0,sta = 0;for(int i=1; i<=m; i++){for(int j=1; j<aux[i]; j++,pos++) {sta|=(1<<pos);}pos++;} // printf("sta=%d\n",sta);h[sta] += ret;} while(next_permutation(aux+1,aux+m+1));return;}for(int i=1; i<=rst&&i<=lst; i++){part[++m] = i;for(int j=0; j<(1<<n); j++) {g[m][j] = g[m-1][j]*f[i][j];}dfs(rst-i,i);m--;} }int main() {for(int i=1; i<(1<<mxN); i++) bitcnt[i] = bitcnt[i>>1]+(i&1);n = read(); for(int i=0; i<n; i++) {scanf("%s",a[i]); for(int j=0; j<n; j++) a[i][j] -= 48;}for(int i=0; i<n; i++) dp[1<<i][i] = 1ll;for(int i=1; i<(1<<n); i++) for(int j=0; j<n; j++) if(i&(1<<j)){llong x = dp[i][j];for(int k=0; k<n; k++) if(a[j][k]&&!(i&(1<<k))){dp[i|(1<<k)][k] += x;}}for(int i=0; i<(1<<n); i++) for(int j=0; j<n; j++) if(i&(1<<j)){f[bitcnt[i]][i] += dp[i][j];} // for(int i=0; i<(1<<n); i++) printf("%I64d ",f[bitcnt[i]][i]); puts("");for(int i=0; i<n; i++) for(int j=0; j<(1<<n); j++) if(j&(1<<i)){for(int k=0; k<n; k++) {f[k][j] += f[k][j^(1<<i)];}}for(int i=0; i<(1<<n); i++) g[0][i] = 1ll; dfs(n,n);for(int i=0; i<n-1; i++) for(int j=0; j<(1<<n-1); j++) if(j&(1<<i)){h[j^(1<<i)] -= h[j];}for(int i=0; i<(1<<n-1); i++) printf("%I64d ",h[i]); puts("");return 0; }總結
以上是生活随笔為你收集整理的Codeforces 1326F Wise Men (容斥原理、状压 DP、子集和变换、划分数)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Codeforces 1329 题解
- 下一篇: Codeforces 1326F Wis