【题解】CF1326F2 Wise Men (Hard Version)
值得一做的数数题(容斥原理,状压DP,子集卷积)
我们要求准确的生成串为 \(s\) 的方案并不好做,考虑转换为求高维前缀和,然后用容斥还原。
这样 \(s_i=1\) 表示必须有边,\(s_i=0\) 表示无限制。
对于任何一个串 \(s\),我们将必须有的边连起来,会得到若干个单链,现在我们要求用若干个大小固定的单链覆盖 \(n\) 个点,不重不漏。
状压 DP 求出 \(f_{s,x}\) 表示以 \(x\) 结尾,经过点集为 \(s\) 的链数,进一步求出 \(g_{s}\) 表示点集为 \(s\) 的链数。
不难发现不重不漏的方案数,本质上就是 \(g\) 数组的子集卷积。
一个串 \(s\) 对应一个链长度的划分方案,我们先对每种划分算出方案,存在哈希表里,然后枚举 \(s\) 求出对应划分的贡献。
\(n=18\) 的划分数并不大,直接暴搜处理即可,最后别忘了还原答案。
#define N 18
void fwt_or(LL *u, int n){
for(int l = 2, k = 1; l <= n; l <<= 1, k <<= 1)
for(int i = 0; i < n; i += l)rep(j, 0, k - 1)
u[i + j + k] += u[i + j];
}
void ifwt_and(LL *u,int n){
for(int l = 2, k = 1; l <= n; l <<= 1, k <<= 1)
for(int i = 0; i < n; i += l)rep(j, 0, k - 1)
u[i + j] -= u[i + j + k];
}
int n, w, d[N][N], bt[1 << N]; LL g[N][1 << N], c[N][1 << N], ans[1 << N], f[N][1 << N];
char ch[N + 5];
unordered_map<int, LL> h;
void dfs(int res,int mn,int cur,int p){
if(!res){
LL sum = 0;
rep(i, 0, w)
(bt[w ^ i] & 1) ? sum -= c[cur - 1][i] : sum += c[cur - 1][i];
h[p] = sum;
}
else{
rep(s, mn, res){
rep(i, 0, w)c[cur][i] = c[cur - 1][i] * g[s - 1][i];
dfs(res - s, s, cur + 1, (p * 1LL * bas + s) % P);
}
}
}
int main() {
read(n);
rep(i, 0, n - 1){
scanf("%s", ch);
rep(j, 0, n - 1)d[i][j] = ch[j] - '0';
f[i][1 << i] = 1;
}
w = (1 << n) - 1;
rp(s, w){
bt[s] = bt[s >> 1] + (1 & s);
if(bt[s] > 1){
rep(i, 0, n - 1)if(1 & (s >> i)){
rep(j, 0, n - 1)if(i != j && (1 & (s >> j)) && d[j][i])
f[i][s] += f[j][s ^ (1 << i)];
g[bt[s] - 1][s] += f[i][s];
}
}else g[0][s]++;
}
rep(i, 0, n - 1)fwt_or(g[i], 1 << n);
rep(i, 0, w)c[0][i] = 1;
dfs(n, 1, 1, 0);
rep(i, 0, w >> 1){
vector<int>u; int res = 1, cur = 0;
rep(j, 0, n - 2)if(1 & (i >> j))res++; else u.pb(res), res = 1;
u.pb(res); sort(u.begin(), u.end());
go(x, u)cur = (cur * 1LL * bas + x) % P;
ans[i] = h[cur];
}
ifwt_and(ans, 1 << (n - 1));
rep(i, 0, w >> 1)printf("%lld ", ans[i]); el;
return 0;
}