【WC2018】州区划分

Description

原题链接

部分分

容易想到 O(3n) O ( 3 n ) 的子集DP
fs=tsftgst f s = ∑ t ⊆ s f t ∗ g s − t
做完了 fs f s 之后还要让 fs=fsinvs f s = f s ∗ i n v s

100%

发现上面那个方程就是一个子集卷积,只不过会自己卷自己的。
回顾子集卷积怎么搞,类似的,设 f[i][s] f [ i ] [ s ]
我们从小到大枚举这个 i i ,相当于分层进行转移。
同理分层FMT就可以了,复杂度是一样的,于是可以O(2nn2)解决。

Codes

#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;++i)
#define fd(i,b,a) for(int i=b;i>=a;--i)
#define efo(i,v,u) for(int i=BB[v],u=B[BB[v]][1];i;i=B[i][0],u=B[i][1])
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define mset(a,x) memset(a,x,sizeof(a))
using namespace std;
typedef long long ll;
char ch;
int read(){int n=0,p=1;for(ch=getchar();ch<'0' || ch>'9';ch=getchar())if(ch=='-') p=-1;for(;'0'<=ch && ch<='9';ch=getchar()) n=n*10+ch-'0';return n*p;}
const int N=23,M=(1<<21)+5,mo=998244353;
ll qmi(ll x,ll n)
{
    ll t=1;
    for(;n;n>>=1,x=x*x%mo) if(n&1) t=t*x%mo;
    return t;
}
bool map[N][N];
int _2[N],n,m,p,in[N],bitcnt[M],fa[N];
int getfa(int x){return fa[x]?fa[x]=getfa(fa[x]):x;}
ll w[N],sum[M],pd[M],f[N][M],g[N][M];
void fmt(ll *a,int sig=1)
{
    fo(i,1,n)
        fo(s,0,_2[n]-1)
            if(s&_2[i-1]) a[s]=(a[s]+(sig>0?(a[s^_2[i-1]]):(mo-a[s^_2[i-1]])))%mo;
}
int main()
{
    int x,y;
    n=read(),m=read(),p=read();_2[0]=1;fo(i,1,n) _2[i]=_2[i-1]<<1;
    fo(i,1,m)
    {
        x=read(),y=read();
        map[x][y]=map[y][x]=1;
    }
    fo(i,1,n) w[i]=read();
    fo(s,1,_2[n]-1)
    {
        fo(i,1,n) if(s&_2[i-1]) sum[s]=(sum[s]+w[i])%mo,++bitcnt[s];
        pd[s]=sum[s]=qmi(sum[s],p);
        sum[s]=qmi(sum[s],mo-2);
        if(bitcnt[s]==1) {pd[s]=0;continue;}
        mset(fa,0);mset(in,0);
        bool ok=0;
        fo(i,1,n) if(s&_2[i-1])
        {
            fo(j,1,n) if(s&_2[j-1])
                if(map[i][j])
                {
                    ++in[i];
                    int fi=getfa(i),fj=getfa(j);
                    if(fi!=fj) fa[fj]=fi;
                }
            if(in[i]&1) {ok=1;break;}
            if(!in[i]) {ok=1;break;}
        }
        int cnt=0;
        fo(i,1,n) if(s&_2[i-1])
            if(i==getfa(i)) ++cnt;
        if(cnt>1) continue;
        if(!ok) pd[s]=0;
    }
    fo(s,1,_2[n]-1) g[bitcnt[s]][s]=pd[s];
    fo(i,0,n) fmt(g[i]);
    f[0][0]=1;
    fo(k,1,n)
    {
        fmt(f[k-1]);
        fo(i,0,k)
            fo(s,0,_2[n]-1) f[k][s]=(f[k][s]+f[i][s]*g[k-i][s])%mo;
        fmt(f[k],-1);
        fo(s,0,_2[n]-1) f[k][s]=f[k][s]*sum[s]%mo;
    }
    printf("%lld\n",f[n][_2[n]-1]);
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值