[WC2018]州区划分

题意

题面

给一张带点权的无向图

要求对其划分为联通且不存在欧拉回路的多个子图

定义一个子图的贡献是

\(i\)个子图的点权和占前\(i\)个子图的点权和的比例的\(p\)次幂

定义一个划分的贡献是

该划分下所有子图的贡献的乘积

求所有划分的贡献之和

题解

\(f_S\)为选取点集为\(S\)时所有划分的贡献和


\[ f_S=\frac{\sum_{T \in S}{f_{S-T}*Sum_T^p}}{Sum_S^p} \]
注意要求\(T\)合法

可以枚举预处理

只需保证联通(并查集)

且没有欧拉回路(所有点度数为偶数)

至此复杂度\(O(3^n)\)

考虑优化

分子部分显然是集合卷积的形式

但与普通的或卷积不同

这个式子要求两个集合交集为空才能转移

这里有一个大智若愚、苦尽甘来、有舍有得、其实就是我太菜了的做法

加一维状态\(i\)表示所选点集的大小

这样复杂度就乘上一个$n^2 $啦

就可以直接或卷积转移

具体说就是

枚举点集\(S\)\(S-T\)大小

再直接FWTFMT转移

这样如果两个集合有交集贡献将是\(0\)

复杂度\(O(n^22^n)\)

#include<bits/stdc++.h>

using namespace std;

#define gc c=getchar()
#define r(x) read(x)
#define ll long long 

template<typename T>
inline void read(T&x){
    x=0;T k=1;char gc;
    while(!isdigit(c)){if(c=='-')k=-1;gc;}
    while(isdigit(c)){x=x*10+c-'0';gc;}x*=k;
}

const int N=25;
const int S=1<<21|7;
const int p=998244353;

struct Edge{
    int u,v;
}E[N*N];

int fa[N];

int find(int x){
    return x==fa[x]?x:fa[x]=find(fa[x]);
}

inline void uni(int u,int v){
    u=find(u),v=find(v);
    if(u!=v)fa[u]=v;
}

inline bool in(int S,int x){
    return S>>(x-1)&1;
}

int n,m,q;

int deg[N];

inline bool check(int S){
    for(int i=1;i<=n;++i){
        fa[i]=i;
        deg[i]=0;
    }
    for(int i=1;i<=m;++i){
        int u=E[i].u,v=E[i].v;
        if(in(S,u)&&in(S,v)){
            uni(u,v);
            ++deg[u];
            ++deg[v];
        }
    }
    int lst=0;
    for(int i=S;i;i^=i&(-i)){
        int x=__builtin_ffs(i);
        if(deg[x]&1)return 1;
        if(lst&&find(x)!=lst)return 1;
        lst=find(x);
    }
    return 0;
}

inline void add(int &a,int b){
    a+=b;
    if(a>=p)a-=p;
}

inline void sub(int &a,int b){
    a-=b;
    if(a<0)a+=p;
}

inline ll qpow(ll a,ll b){
    ll ans=1;
    while(b){
        if(b&1)(ans*=a)%=p;
        (a*=a)%=p;
        b>>=1;
    }
    return ans;
}

inline void fmt(int *A){
    for(int i=0;i<n;++i){
        for(int j=0;j<1<<n;++j){
            if(j>>i&1)add(A[j],A[j^(1<<i)]);
        }
    }
}

inline void ifmt(int *A){
    for(int i=0;i<n;++i){
        for(int j=0;j<1<<n;++j){
            if(j>>i&1)sub(A[j],A[j^(1<<i)]);
        }
    }
}

int w[N];
int sum[S];
int Inv[S];
int bit[S];
int g[N][S];
int f[N][S];

int main(){
    r(n),r(m),r(q);
    for(int i=1;i<=m;++i){
        int u,v;r(u),r(v);
        E[i]=Edge{u,v};
    }
    for(int i=1;i<=n;++i){
        r(w[i]);
    }
    int S=(1<<n)-1;
    for(int i=1;i<=S;++i){
        sum[i]=w[__builtin_ffs(i)]+sum[i^(i&(-i))];
        bit[i]=bit[i>>1]+(i&1);
        int tmp=qpow(sum[i],q);
        g[bit[i]][i]=check(i)*tmp;
        Inv[i]=qpow(tmp,p-2);
    }
    for(int i=1;i<=n;++i)fmt(g[i]);
    f[0][0]=1;fmt(f[0]);
    for(int i=1;i<=n;++i){
        for(int j=0;j<i;++j){
            for(int k=0;k<1<<n;++k){
                add(f[i][k],(ll)f[j][k]*g[i-j][k]%p);
            }
        }
        ifmt(f[i]);
        for(int k=0;k<1<<n;++k)f[i][k]=i==bit[k]?(ll)f[i][k]*Inv[k]%p:0;
        if(i^n)fmt(f[i]);
    }
    printf("%d\n",f[n][S]);
}

转载于:https://www.cnblogs.com/yicongli/p/10185234.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值