[LOJ#2340] [WC2018] 州区划分

题目链接

洛谷题面

LOJ题面还是LOJ机子比较快

Solution

\(f(s)\)表示选\(s\)这些城市的总代价,那么我们可以得到一个比较显然的\(dp\)
\[ f(s)=\frac{1}{sum_s^p}\sum_{t\subset s} f(t)g(s-t) \]
其中\(g(i)\)表示合法的\(sum_i^p\),即若不合法则为\(0\)

这个\(dp\)\(O(3^n)\)的,注意到这是个子集卷积的形式,我们可以考虑用\(fwt\)优化它。

那么我们可以一层一层的\(dp\),即按照\(bitcnt(s)\)的顺序从小到大计算\(f\),假设当前算到了\(bitcnt=i\),那么那些\(bitcnt<i\)\(f\)肯定已经算完了,所以我们可以用\(fwt\)加速来算好当前层的\(f\)

容易发现这样做的复杂度是\(O(n^22^n)\)的,足以通过此题。

洛谷机子好慢啊...要开O2才能过,loj一半时限就过了

#include<bits/stdc++.h>
using namespace std;
 
void read(int &x) {
    x=0;int f=1;char ch=getchar();
    for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-f;
    for(;isdigit(ch);ch=getchar()) x=x*10+ch-'0';x*=f;
}
 
void print(int x) {
    if(x<0) putchar('-'),x=-x;
    if(!x) return ;print(x/10),putchar(x%10+48);
}
void write(int x) {if(!x) putchar('0');else print(x);putchar('\n');}

#define lf double
#define ll long long 

const int maxn = (1<<21)+10;
const int inf = 1e9;
const lf eps = 1e-8;
const int mod = 998244353;

int cnt[maxn],sum[maxn],g[22][maxn];
int f[22][maxn],n,m,p,w[30],fa[30],d[30],head[30],tot,inv[maxn];
struct edge{int to,nxt;}e[900];

void Add(int u,int v) {e[++tot]=(edge){v,head[u]},head[u]=tot;}
void ins(int u,int v) {Add(u,v),Add(v,u);}

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

int add(int x,int y) {return x+y>mod?x+y-mod:x+y;}
int del(int x,int y) {return x-y<0?x-y+mod:x-y;}
int mul(int x,int y) {return 1ll*x*y-1ll*x*y/mod*mod;}

int check(int s) {
    if(cnt[s]==1) return 0;
    for(int i=1;i<=n;i++) fa[i]=i,d[i]=0;
    for(int x=1;x<=n;x++)
        if(s>>(x-1)&1) {
            for(int u,v,i=head[x];i;i=e[i].nxt) {
                if(!(s>>(e[i].to-1)&1)) continue;
                d[e[i].to]++;
                if((u=find(x))!=(v=find(e[i].to))) fa[u]=v; 
            }
        }
    int rt=__builtin_ctz(s&-s)+1;
    for(int i=1;i<=n;i++) if(s>>(i-1)&1) if(find(i)!=find(rt)||(d[i]&1)) return 1;
    return 0;
}

void fwt(int *r,int N) {
    for(int i=1;i<N;i<<=1)
        for(int j=0;j<N;j+=i<<1)
            for(int k=0;k<i;k++)
                r[i+j+k]=add(r[i+j+k],r[j+k]);
}

void ifwt(int *r,int N) {
    for(int i=1;i<N;i<<=1)
        for(int j=0;j<N;j+=i<<1)
            for(int k=0;k<i;k++)
                r[i+j+k]=del(r[i+j+k],r[j+k]);
}

int qpow(int a,int x) {
    int res=1;
    for(;x;x>>=1,a=mul(a,a)) if(x&1) res=mul(res,a);
    return res;
}

int main() {
    read(n),read(m),read(p);
    for(int i=1,x,y;i<=m;i++) read(x),read(y),ins(x,y);
    for(int i=1;i<=n;i++) read(w[i]);
    int all=1<<n;
    for(int i=1;i<all;i++) cnt[i]=__builtin_popcount(i);
    for(int i=1;i<all;i++) sum[i]=w[__builtin_ctz(i&-i)+1]+sum[i^(i&-i)];
    for(int i=1;i<all;i++) sum[i]=qpow(sum[i],p);
    for(int i=0;i<all;i++) g[cnt[i]][i]=sum[i]*check(i),inv[i]=qpow(sum[i],mod-2);
    for(int i=0;i<=n;i++) fwt(g[i],all);
    f[0][0]=1;fwt(f[0],all);
    for(int i=1;i<=n;i++) {
        for(int j=0;j<i;j++)
            for(int s=0;s<all;s++)
                f[i][s]=add(f[i][s],mul(f[j][s],g[i-j][s]));
        ifwt(f[i],all);
        for(int s=0;s<all;s++) f[i][s]=cnt[s]==i?mul(f[i][s],inv[s]):0;
        if(i!=n) fwt(f[i],all);
    }
    write(f[n][all-1]);
    return 0;
}

转载于:https://www.cnblogs.com/hbyer/p/10582952.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值