题目链接
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;
}