[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;
}

浙公网安备 33010602011771号