[WC2018]州区划分(FWT_OR卷积)

problem

洛谷链接

solution

显然题目指向:存在欧拉回路的州划分是不合法的。

当且仅当这个州是 联通 的且 内部没有奇数度数的点 时,这个州不合法。

因为 n n n 非常小,我们可以枚举每一种州划分方案,判断是否合法,顺便记录每个划分方案的贡献。

f ( s ) : f(s): f(s): 当前划分集合为 s s s 的答案, g ( s ) : g(s): g(s): 划分一个州为 s s s 是否合法, v a l ( s ) : val(s): val(s): 划分州为 s s s 的贡献。

列出状态转移方程: f ( s ) = ∑ t ∈ s f ( s − t ) g ( t ) ( v a l ( t ) v a l ( s ) ) p f(s)=\sum_{t\in s}f(s-t)g(t)\Big(\frac{val(t)}{val(s)}\Big)^p f(s)=tsf(st)g(t)(val(s)val(t))p

显然能看到有卷积的形式,即 f ( s ) = ∑ i ⋂ j = ∅ ∧ i ⋃ j = s f ( j ) g ( i ) ( v a l ( i ) v a l ( s ) ) p f(s)=\sum_{i\bigcap j=\empty\wedge i\bigcup j=s}f(j)g(i)\Big(\frac{val(i)}{val(s)}\Big)^p f(s)=ij=ij=sf(j)g(i)(val(s)val(i))p

i ⋃ j = s i\bigcup j=s ij=s 可以直接用 FWT \text{FWT} FWT 做,但是 i ⋂ j = ∅ i\bigcap j=\empty ij= 就不好做了。

如果结合并集为 s s s 的信息,也就意味着 ∣ i ∣ + ∣ j ∣ = ∣ s ∣ |i|+|j|=|s| i+j=s

对于 d p dp dp 的设计可以再加一维二进制状态中 1 1 1 的个数。

f ( i , s ) : s f(i,s):s f(i,s):s 1 1 1 的个数为 i i i 的答案, g ( i , s ) : [ s g(i,s):[s g(i,s):[s 成立 ] ∗ v a l ( s ) ]*val(s) ]val(s)

f ( i , s ) g ( i , s ) = ∑ j = 0 i − 1 ∑ t ∈ s f ( j , s − t ) g ( i − j , t ) f(i,s)g(i,s)=\sum_{j=0}^{i-1}\sum_{t\in s}f(j,s-t)g(i-j,t) f(i,s)g(i,s)=j=0i1tsf(j,st)g(ij,t)

预处理贡献和贡献的逆元,状态是否合法,直接 FWT \text{FWT} FWT 卷积即可。

code

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define mod 998244353
int n, m, p, N;
int u[250], v[250], w[25], fa[25], d[25], val[1 << 21], tot[1 << 21], inv[1 << 21];
bool ok[1 << 21];
int f[22][1 << 21], g[22][1 << 21];

int qkpow( int x, int y ) {
    int ans = 1;
    while( y ) {
        if( y & 1 ) ans = ans * x % mod;
        x = x * x % mod;
        y >>= 1;
    }
    return ans;
}

void fwt( int *c, int f ) {
    for( int i = 1;i < N;i <<= 1 )
        for( int j = 0;j < N;j += ( i << 1 ) )
            for( int k = 0;k < i;k ++ )
                c[j + k + i] = ( c[j + k + i] + c[j + k] * f % mod + mod ) % mod;
}

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

signed main() {
    scanf( "%lld %lld %lld", &n, &m, &p );
    for( int i = 1;i <= m;i ++ ) scanf( "%lld %lld", &u[i], &v[i] );
    for( int i = 1;i <= n;i ++ ) scanf( "%lld", &w[i] );
    N = 1 << n;
    for( int s = 0;s < N;s ++ ) {
        int num = 0;
        for( int i = 1;i <= n;i ++ ) {
            if( 1 << i - 1 & s ) num ++, val[s] += w[i];
            d[i] = 0, fa[i] = i;
        }
        tot[s] = num;
        for( int i = 1;i <= m;i ++ )
            if( 1 << u[i] - 1 & s and 1 << v[i] - 1 & s ) {
                int fu = find( u[i] ), fv = find( v[i] );
                if( fu ^ fv ) num --, fa[fv] = fu;
                d[u[i]] ++, d[v[i]] ++;
            }
        if( num ^ 1 ) ok[s] = 1;
        num = 0;
        for( int i = 1;i <= n;i ++ ) num += d[i] & 1;
        if( num ) ok[s] = 1;
        if( ok[s] ) g[tot[s]][s] = qkpow( val[s], p );
        inv[s] = qkpow( qkpow( val[s], p ), mod - 2 );
    }
    for( int i = 0;i <= n;i ++ ) fwt( g[i], 1 );
    f[0][0] = 1;
    fwt( f[0], 1 );
    for( int i = 1;i <= n;i ++ ) {
        for( int j = 0;j < i;j ++ )
            for( int k = 0;k < N;k ++ )
                f[i][k] = ( f[i][k] + f[j][k] * g[i - j][k] ) % mod;
        fwt( f[i], -1 );
        for( int j = 0;j < N;j ++ ) f[i][j] = tot[j] == i ? f[i][j] * inv[j] % mod : 0;
        fwt( f[i], 1 ); 
    }
    printf( "%lld\n", f[n][N - 1] );
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值