Luogu4221 WC2018州区划分(状压dp+FWT)

时间:2023-03-09 17:36:51
Luogu4221 WC2018州区划分(状压dp+FWT)

  合法条件为所有划分出的子图均不存在欧拉回路或不连通,也即至少存在一个度数为奇数的点或不连通。显然可以对每个点集预处理是否合法,然后就不用管这个奇怪的条件了。

  考虑状压dp。设f[S]为S集合所有划分方案的满意度之和,枚举子集转移,则有f[S]=Σg[S']*f[S^S']*(sum[S']/sum[S])(S'⊆S),其中g[S]为S集合是否合法,sum[S]为S集合人口数之和。复杂度O(3n)。这个式子非常显然,就这么送了50分。p这么小显得非常奇怪但也没有任何卵用。

  考虑优化。转移方程写的更优美一点大约是f[S]=Σf[x]*g[y]/h[S] (x|y=S,x&y=0)。看起来像是一个或卷积,但还有后面一个限制。考虑在x|y=S的前提下,x&y=0实际上相当于|x|+|y|=|S|。于是稍微改一下状态,f[i][S]为i个点所选点集为S时的满意度之和(虽然第一维显然是可以由第二维推出的),g同样更改状态,这样转移就是f[i][S]=Σf[u][x]*g[v][y]/h[S] (x|y=S,u+v=i)。暴力枚举第一维u,FWT做或卷积即可,复杂度O(2n·n2)。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
#define ll long long
#define N 21
#define P 998244353
#define rep(i,t,S) for (int t=S,i=lg2[t&-t];t;t^=t&-t,i=lg2[t&-t])
int gcd(int n,int m){return m==?n:gcd(m,n%m);}
int read()
{
int x=,f=;char c=getchar();
while (c<''||c>'') {if (c=='-') f=-;c=getchar();}
while (c>=''&&c<='') x=(x<<)+(x<<)+(c^),c=getchar();
return x*f;
}
int n,m,p,a[N][N],w[N],fa[N],degree[N],lg2[<<N],sum[<<N],size[<<N],f[N+][<<N],g[N+][<<N];
int ksm(int a,int k)
{
int s=;
for (;k;k>>=,a=1ll*a*a%P) if (k&) s=1ll*s*a%P;
return s;
}
int inv(int a){return ksm(a,P-);}
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
void inc(int &x,int y){x+=y;if (x>=P) x-=P;}
void get()
{
for (int i=;i<n;i++) lg2[<<i]=i;
for (int i=;i<(<<n);i++)
{
sum[i]=sum[i^(i&-i)]+w[lg2[i&-i]];
size[i]=size[i^(i&-i)]+;
rep(x,u,i) fa[x]=x,degree[x]=;
rep(x,u,i)
{
rep(y,v,i) if (a[x][y]) degree[x]^=,fa[find(x)]=find(y);
if (degree[x]) {g[size[i]][i]=;break;}
}
int f=-;
rep(x,u,i) if (f==-) f=find(x);else if (f!=find(x)) {g[size[i]][i]=;break;}
}
for (int i=;i<(<<n);i++) sum[i]=ksm(sum[i],p),g[size[i]][i]*=sum[i];
}
void FWT(int *a,int n,int op)
{
for (int i=;i<=n;i<<=)
for (int j=;j<n;j+=i)
for (int k=j;k<j+(i>>);k++)
if (!op) a[k+(i>>)]=(a[k+(i>>)]+a[k])%P;
else a[k+(i>>)]=(a[k+(i>>)]-a[k]+P)%P;
}
void solve()
{
f[][]=;
FWT(f[],<<n,);
for (int i=;i<=n;i++) FWT(g[i],<<n,);
for (int i=;i<=n;i++)
{
for (int j=;j<(<<n);j++)
for (int x=;x<i;x++)
f[i][j]=(f[i][j]+1ll*f[x][j]*g[i-x][j])%P;
FWT(f[i],<<n,);
for (int j=;j<(<<n);j++)
if (size[j]==i) f[i][j]=1ll*f[i][j]*inv(sum[j])%P;
FWT(f[i],<<n,);
}
FWT(f[n],<<n,);
cout<<f[n][(<<n)-];
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
const char LL[]="%I64d\n";
#else
const char LL[]="%lld\n";
#endif
n=read(),m=read(),p=read();
for (int i=;i<=m;i++)
{
int x=read()-,y=read()-;
a[x][y]=a[y][x]=;
}
for (int i=;i<n;i++) w[i]=read();
get();
solve();
return ;
}