題意
有個點,有些點之間有邊。對於到的每個排列,構造長度爲的串,其中當且僅當和之間有邊。對每個長度爲的串,求有多少個排列構造出來的串等於。
分析
考慮對每個串求它超集的答案之和,然後dp一次得到答案。
如果我們把有邊相連且在排列中相鄰的點連在一起,把序列分成了若干段,滿足每一段的長度之和爲。可以發現分段排序後相同的串它們的答案是一樣的,故只需要對的每種整數劃分方案,求出該方案的答案即可。
先預處理出表示選出一條由個人組成的路徑的方案,其中是一個二進制狀態,表示選了哪個人。那麼的某種劃分的答案就是,其中且的並集爲全集。
但這樣還是不好求。考慮求出表示的並集爲的子集的貢獻,然後通過容斥來求總的貢獻。對求子集和,就有,就做完了。
寫法上還有一點優化,就是在dfs處理的整數劃分的時候,可以邊搜邊處理,複雜度就變成了,其中表示的整數劃分數目。
代碼
#include<bits/stdc++.h>
#define pb push_back
typedef long long LL;
const int N=23;
const int M=300005;
int n,bin[N],a[N][N],cnt[M];
LL dp[N][M],f[M],g[N][M],d[N][M];
std::map<int,LL> ma;
void pre()
{
for (int i=1;i<=n;i++) dp[i][bin[i-1]]=1;
for (int i=1;i<bin[n];i++)
for (int j=1;j<=n;j++)
if (dp[j][i])
{
g[cnt[i]][i]+=dp[j][i];
for (int k=1;k<=n;k++)
if (!(i&bin[k-1])&&a[j][k]) dp[k][i+bin[k-1]]+=dp[j][i];
}
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
for (int k=0;k<bin[n];k++)
if (k&bin[j-1]) g[i][k]+=g[i][k^bin[j-1]];
}
void calc(int dep,LL sta)
{
LL w=0;
for (int i=0;i<bin[n];i++)
if ((n-cnt[i])&1) w-=d[dep][i];
else w+=d[dep][i];
ma[sta]=w;
}
void dfs(int mx,int sum,int used,int dep,LL sta)
{
if (sum==n) {calc(dep,sta);return;}
if (sum+mx>n) return;
for (int i=0;i<bin[n];i++) d[dep+1][i]=d[dep][i]*g[mx][i];
dfs(mx,sum+mx,used+1,dep+1,sta*10+mx);
dfs(mx+1,sum,0,dep,sta);
}
LL get_sta(int x)
{
std::vector<int> vec;
int s=0;
for (int i=0;i<n;i++)
if (x&bin[i]) s++;
else vec.pb(s+1),s=0;
std::sort(vec.begin(),vec.end());
LL sta=0;
for (int i=0;i<vec.size();i++) sta=sta*10+vec[i];
return sta;
}
void solve()
{
for (int i=0;i<bin[n-1];i++) f[i]=ma[get_sta(i)];
for (int i=1;i<n;i++)
for (int j=0;j<bin[n-1];j++)
if (!(j&bin[i-1])) f[j]-=f[j^bin[i-1]];
for (int i=0;i<bin[n-1];i++) printf("%lld ",f[i]);
}
int main()
{
scanf("%d",&n);
bin[0]=1;
for (int i=1;i<=n;i++) bin[i]=bin[i-1]*2;
for (int i=1;i<bin[n];i++) cnt[i]=cnt[i^(i&(-i))]+1;
for (int i=1;i<=n;i++)
{
char str[N];
scanf("%s",str+1);
for (int j=1;j<=n;j++) a[i][j]=str[j]-'0';
}
pre();
for (int i=0;i<bin[n];i++) d[0][i]=1;
dfs(1,0,0,0,0);
solve();
return 0;
}