[WC2018] 州区划分 题解

[WC2018] 州区划分

先判不合法,发现一个州如果是欧拉回路就不合法,即所有点度数为偶数且连通。判连通性可以用这样一段代码来做到。

bool check(int s){
    int cur=s&(-s),pre=0;
    while(cur!=pre){
        pre=cur;
        for(int i=0;i<n;i++){
            if((cur>>i)&1){
                cur|=arv[i+1]&s;
            }
        }
    }
    return cur==s;
}

随即需要发现的重要观察是 \(\left(\dfrac{\sum _ {x \in V _ i} w _ x}{\sum _ {j = 1} ^ i \sum _ {x \in V _ j} w _ x}\right) ^ p\),这个式子的分母和前面的集合具体怎么划分没有关系,只与前面有哪些点被划分了有关系,所以我们可以设计一个状压 DP。设 \(f_s\) 表示划分用到了点集 \(s\) 时的答案,转移为 \(f_s=\sum_{t\subseteq s} f_t \times \dfrac{val_{s/t}}{val_s}\),其中 \(val_s\) 就是 \((\sum _ {x \in s} w _ x)^p\),可以预处理。时间复杂度 \(O(3^n)\),无法通过。

for(int s=0;s<(1<<n);s++){
    for(int t=(s-1)&s;;t=(t-1)&s){
        int r=s^t;
        add(f[s],inv[s]*f[t]%mod*g[r]%mod);
        if(!t)break;
    }
}

观察式子,发现原式等于 \(f_s=\dfrac{1}{val_s} \sum_{t \subseteq s} f_t\times val_{s/t}\),后面这部分是一个子集卷积的形式,可以用 FMT 优化到 \(O(n^22^n)\)。

有几点要注意的:
1.\(inv_s\) 不能和 FMT 变换后的数组相乘,只能把结果逆变换回来之后再乘。
2.\(f_{i,s}\) 是每次循环都把 \(f_{i-1}\) 变换一次。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define _int __int128
#define ull unsigned long long
#define pii pair<int,int>
#define fst first
#define scd second
#define pq priority_queue
#define mkp make_pair
#define popcount(x) __builtin_popcount(x)
#define endl '\n'
int n,m,p;
const int N = (1<<21)+10,mod=998244353;
int w[22],arv[23],ilg[N],val[N],g[22][N],f[22][N],inv[N],pc[N],tmp[22][N];
inline void add(ll &x,ll y){
    x=(x+y>=mod)?x+y-mod:x+y;
}
ll qpow(ll a,int b){
    ll res=1;
    while(b){
        if(b&1)res=(res*a)%mod;
        a=(a*a)%mod;
        b>>=1;
    }
    return res;
}
void FMT(int *a,int op){
    for(int i=0;i<n;i++){
        for(int s=0;s<(1<<n);s++){
            if((s>>i)&1){
                ll res=a[s];
                add(res,(1ll*op*a[s^(1<<i)]+mod)%mod);
                a[s]=res;
            }
        }
    }
}
bool check(int s){
    int cur=s&(-s),pre=0;
    while(cur!=pre){
        pre=cur;
        for(int i=0;i<n;i++){
            if((cur>>i)&1){
                cur|=arv[i+1]&s;
            }
        }
    }
    return cur==s;
}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n>>m>>p;
    for(int i=1;i<=m;i++){
        int u,v;
        cin>>u>>v;
        arv[u]|=(1<<v-1);
        arv[v]|=(1<<u-1);
    }
    for(int i=1;i<=n;i++){
        cin>>w[i];
    }
    for(int i=0;i<(1<<n);i++)pc[i]=popcount(i);
    for(int s=0;s<(1<<n);s++){
        ilg[s]=0;
        for(int i=1;i<=n;i++){
            if((s>>i-1)&1){
                val[s]+=w[i];
                int x=arv[i]&(s);
                ilg[s]|=(popcount(x)&1);
            }
        }
        if(!check(s))ilg[s]=1;
        g[pc[s]][s]=qpow(val[s],p);
        inv[s]=qpow(g[pc[s]][s],mod-2);
        if(!ilg[s])g[pc[s]][s]=0;
    }
    f[0][0]=1;
    for(int i=0;i<=n;i++)FMT(g[i],1);
    for(int i=1;i<=n;i++){
        FMT(f[i-1],1);
        for(int s=0;s<(1<<n);s++){
            ll res=0;
            for(int j=1;j<=i;j++){
                add(res,1ll*f[i-j][s]*g[j][s]%mod);
            }
            tmp[i][s]=res;
        }
        FMT(tmp[i],-1);
        for(int s=0;s<(1<<n);s++){
            if(pc[s]==i)f[i][s]=1ll*tmp[i][s]*inv[s]%mod;
        }
    }
    cout<<f[n][(1<<n)-1];
    return 0;
}
posted @ 2026-10-09 15:24  farenara_sayowell  阅读(4)  评论(0)    收藏  举报