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;
    }
     
  • 相关阅读:
    关于程序出现 “因为应用程序正在发送一个输入同步呼叫,所以无法执行传出的呼叫”
    循环物理依赖
    重新生成执行计划
    SQL SERVER 2008 存储过程传表参数
    关于operator void* 操作符
    关于C++编译时内链接和外链接
    低级键盘钩子,在WIN7以上版本的问题
    关于SendMessage和PostMessage的理解的例子
    一个简单代码
    GET 和 POST 比较整理
  • 原文地址:https://www.cnblogs.com/Gloid/p/10268671.html
Copyright © 2011-2022 走看看