【UOJ348】【WC2018】州区划分 状压DP FWT

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/ez_yww/article/details/79314605

题目大意

  给定⼀个n个点的⽆向图,对于⼀种n个点的划分{S1,S2,,Sk},定义它是合法的,当且仅当每个点都在其中的一个集合中且对于任何的i[1,k],点集Si⾮空,且导出⼦图不存在欧拉回路。

  给定数组wi,求对于所有合法的划分{s1,s2,,sk},下面的式子之和

(i=1kxSiwxj=1ixSjwx)p

  n21

题解

  先用O(n22n)判断每个点集是否合法,并计算g(S)=(xSwx)p。如果S不合法。那么g(S)=0

  很容易想到一个O(3n)的做法。

  设f(S)为当前选择的集合为S的答案

f(S)=TSg(T)f(ST)g(S)

  可以发现这是一个子集卷积。

  一种可行的做法是把子集卷积转化为子集或卷积。

  定义f~f的集合占位幂级数,当且仅当对于所有的S满足f~(S)是一个|S|次多项式,且[x|S|]f~(S)=f(S)

  可以发现,若p(S)=f~(S)g~(S),则pf×g的占位多项式。

  所以我们可以在O(n22n)内计算子集卷积了。

  回到这道题,我们假设每个城市可以出现在多个州里,记hi,S为每个州的城市个数之和为i,每个州的城市的并集为S的方案数。那么F(S)=i=1nhi,Sxi就是f(S)的集合占位幂级数。

  所以f(S)=h|S|,S,状态转移方程是

hi,S=j=1i|T|=jA[A|T=S]hij,Ag(T)g(S)

  记G(i)=|S|=ig(S)xS

  我们先枚举i,再枚举j,然后计算hi=hij×G(j)

  这里我们可以全程用莫比乌斯变换后的值,卷积一次就是O(2n)

  还有,我们这里要除以g(S),可以在做完一层(hi)之后变换回去,除以g(S),再变换回来。

  这样时间复杂度就是O(n22n)的了。

代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<ctime>
#include<cstdlib>
#include<utility>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
void open(const char *s)
{
#ifndef ONLINE_JUDGE
    char str[100];
    sprintf(str,"%s.in",s);
    freopen(str,"r",stdin);
    sprintf(str,"%s.out",s);
    freopen(str,"w",stdout);
#endif
}
const ll p=998244353;
ll fp(ll a,ll b)
{
    ll s=1;
    for(;b;b>>=1,a=a*a%p)
        if(b&1)
            s=s*a%p;
    return s;
}
int lx[100010];
int ly[100010];
int n,m,o;
void fwt(int *a)
{
    int i,j;
    for(j=1;j<1<<n;j<<=1)
        for(i=0;i<1<<n;i++)
            if(i&j)
                a[i]=(a[i]+a[i^j])%p;
}
void ifwt(int *a)
{
    int i,j;
    for(j=1;j<1<<n;j<<=1)
        for(i=0;i<1<<n;i++)
            if(i&j)
                a[i]=(a[i]-a[i^j])%p;
}
int f[22][1<<21];
int g[22][1<<21];
ll inv[100010];
ll fw[1<<21];
ll fwi[1<<21];
int w[30];
int fa[100010];
int num[1<<21];
int find(int x)
{
    return fa[x]==x?x:fa[x]=find(fa[x]);
}
int c[1<<21];
int d[100010];
int main()
{
    open("walk");
    int i,j,k;
    inv[0]=inv[1]=1;
    for(i=2;i<=10000;i++)
        inv[i]=-p/i*inv[p%i]%p;
    scanf("%d%d%d",&n,&m,&o);
    for(i=1;i<=m;i++)
        scanf("%d%d",&lx[i],&ly[i]);
    for(i=1;i<=n;i++)
        scanf("%d",&w[i]);
    int all=(1<<n)-1;
    for(i=1;i<=all;i++)
    {
        for(j=1;j<=n;j++)
        {
            d[j]=0;
            fa[j]=j;
        }
        for(j=1;j<=m;j++)
            if(((i>>(lx[j]-1))&1)&&((i>>(ly[j]-1))&1))
            {
                d[lx[j]]^=1;
                d[ly[j]]^=1;
                int fx=find(lx[j]);
                int fy=find(ly[j]);
                if(fx!=fy)
                    fa[fx]=fy;
            }
        fw[i]=0;
        for(j=1;j<=n;j++)
            if((i>>(j-1))&1)
                fw[i]+=w[j];
        fwi[i]=inv[fw[i]];
        fw[i]=fp(fw[i],o);
        fwi[i]=fp(fwi[i],o);
        int cnt=0;
        c[i]=0;
        for(j=1;j<=n;j++)
            if((i>>(j-1))&1)
            {
                num[i]++;
                if(fa[j]==j)
                {
                    cnt++;
                    if(cnt>=2)
                        c[i]=1;
                }
                if(d[j])
                    c[i]=1;
            }
        fw[i]*=c[i];
        g[num[i]][i]=fw[i];
    }
    f[0][0]=1;
    for(i=1;i<=n;i++)
        fwt(g[i]);
    fwt(f[0]);
    for(i=1;i<=n;i++)
    {
        for(j=1;j<=i;j++)
            for(k=0;k<=all;k++)
                f[i][k]=(f[i][k]+(ll)f[i-j][k]*g[j][k])%p;
        ifwt(f[i]);
        for(j=0;j<=all;j++)
            f[i][j]=f[i][j]*fwi[j]%p;
        fwt(f[i]);
    }
    ifwt(f[n]);
    ll ans=f[n][all];
    ans=(ans+p)%p;
    printf("%lld\n",ans);
    return 0;
}
阅读更多
换一批

没有更多推荐了,返回首页