1. 程式人生 > >Luogu4221 WC2018州區劃分(狀壓dp+FWT)

Luogu4221 WC2018州區劃分(狀壓dp+FWT)

+= 就是 online lse etc \n ring 不用 degree

  合法條件為所有劃分出的子圖均不存在歐拉回路或不連通,也即至少存在一個度數為奇數的點或不連通。顯然可以對每個點集預處理是否合法,然後就不用管這個奇怪的條件了。

  考慮狀壓dp。設f[S]為S集合所有劃分方案的滿意度之和,枚舉子集轉移,則有f[S]=Σg[S‘]*f[S^S‘]*(sum[S‘]/sum[S])p (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==0?n:gcd(m,n%m);} int read() { int x=0,f=1;char c=getchar(); while (c<0||c>9) {if (c==-) f=-1;c=getchar();} while (c>=0&&c<=9) x=(x<<1)+(x<<3)+(c^48),c=getchar(); return x*f; } int n,m,p,a[N][N],w[N],fa[N],degree[N],lg2[1
<<N],sum[1<<N],size[1<<N],f[N+1][1<<N],g[N+1][1<<N]; int ksm(int a,int k) { int s=1; for (;k;k>>=1,a=1ll*a*a%P) if (k&1) s=1ll*s*a%P; return s; } int inv(int a){return ksm(a,P-2);} 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=0;i<n;i++) lg2[1<<i]=i; for (int i=1;i<(1<<n);i++) { sum[i]=sum[i^(i&-i)]+w[lg2[i&-i]]; size[i]=size[i^(i&-i)]+1; rep(x,u,i) fa[x]=x,degree[x]=0; rep(x,u,i) { rep(y,v,i) if (a[x][y]) degree[x]^=1,fa[find(x)]=find(y); if (degree[x]) {g[size[i]][i]=1;break;} } int f=-1; rep(x,u,i) if (f==-1) f=find(x);else if (f!=find(x)) {g[size[i]][i]=1;break;} } for (int i=0;i<(1<<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=2;i<=n;i<<=1) for (int j=0;j<n;j+=i) for (int k=j;k<j+(i>>1);k++) if (!op) a[k+(i>>1)]=(a[k+(i>>1)]+a[k])%P; else a[k+(i>>1)]=(a[k+(i>>1)]-a[k]+P)%P; } void solve() { f[0][0]=1; FWT(f[0],1<<n,0); for (int i=0;i<=n;i++) FWT(g[i],1<<n,0); for (int i=1;i<=n;i++) { for (int j=0;j<(1<<n);j++) for (int x=0;x<i;x++) f[i][j]=(f[i][j]+1ll*f[x][j]*g[i-x][j])%P; FWT(f[i],1<<n,1); for (int j=0;j<(1<<n);j++) if (size[j]==i) f[i][j]=1ll*f[i][j]*inv(sum[j])%P; FWT(f[i],1<<n,0); } FWT(f[n],1<<n,1); cout<<f[n][(1<<n)-1]; } 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=1;i<=m;i++) { int x=read()-1,y=read()-1; a[x][y]=a[y][x]=1; } for (int i=0;i<n;i++) w[i]=read(); get(); solve(); return 0; }

Luogu4221 WC2018州區劃分(狀壓dp+FWT)