zoukankan      html  css  js  c++  java
  • 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==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
    ";
    #else
        const char LL[]="%lld
    ";
    #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;
    }
     
  • 相关阅读:
    Window 窗口类
    使用 Bolt 实现 GridView 表格控件
    lua的table库
    Windows编程总结之 DLL
    lua 打印 table 拷贝table
    使用 xlue 实现简单 listbox 控件
    使用 xlue 实现 tips
    extern “C”
    COleVariant如何转换为int double string cstring
    原来WIN32 API也有GetOpenFileName函数
  • 原文地址:https://www.cnblogs.com/Gloid/p/10268671.html
Copyright © 2011-2022 走看看