1. 程式人生 > >【UOJ#422】【集訓隊作業2018】小Z的禮物(min-max容斥,輪廓線dp)

【UOJ#422】【集訓隊作業2018】小Z的禮物(min-max容斥,輪廓線dp)

【UOJ#422】【集訓隊作業2018】小Z的禮物(min-max容斥,輪廓線dp)

題面

UOJ

題解

毒瘤xzy,怎麼能搬這種題當做WC模擬題QwQ
一開始開錯題了,根本就不會做。
後來發現是每次任意覆蓋相鄰的兩個,那麼很明顯就可以套\(min-max\)容斥。
要求的就是\(max(All)\),而每個集合的\(min\)是很好求的。
如果直接暴力列舉集合複雜度就是\(2^{cnt}cnt\)
仔細想想每個子集我們要知道的是什麼,只需要知道子集大小來確定前面的容斥係數,還需要知道覆蓋子集的方案數,這樣就可以知道\(min\)的概率,倒數就是期望了。
而覆蓋子集的方案數不會超過\(2*n*m-n-m\)

,顯然要比\(2^{cnt}\)優秀。
所以列舉覆蓋子集的方案數來\(dp\),至於子集大小之和容斥係數相關,而容斥係數只有正負\(1\),所以直接乘進去一起轉移就好了,不需要單獨存一維狀態。
考慮每次新加入一個點之後的覆蓋方案,只需要知道當前位置四周是否已經存在於子集當中,那麼直接狀壓當前的輪廓線就好了。
設狀態\(f[S][k]\)表示覆蓋方案數為\(k\),輪廓線為\(S\)時的方案數,容斥係數已經考慮進去。
顯然當前位置可以不選,那麼直接轉移。
如果當前位置可以選入子集,那麼乘上係數\(-1\),同時修改覆蓋方案數以及輪廓線的狀態轉移。
最後按照\(min-max\)容斥統計答案即可。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define MOD 998244353
void add(int &x,int y){x+=y;if(x>=MOD)x-=MOD;}
char g[200][200];
int n,m,inv[1500],sum,pw,nw,ans,S;
int f[2][1<<6][1200];
int main()
{
    scanf("%d%d",&n,&m);S=1<<n;sum=2*n*m-n-m;
    for(int i=1;i<=n;++i)scanf("%s",g[i]+1);
    inv[0]=inv[1]=1;for(int i=2;i<1500;++i)inv[i]=1ll*inv[MOD%i]*(MOD-MOD/i)%MOD;
    f[0][0][0]=MOD-1;
    for(int i=1;i<=m;++i)
        for(int j=1;j<=n;++j)
        {
            pw=nw;nw^=1;memset(f[nw],0,sizeof(f[nw]));
            for(int T=0;T<S;++T)
                for(int k=0;k<=sum;++k)
                    if(f[pw][T][k])
                    {
                        int nT=T&((S-1)^(1<<(j-1)));
                        add(f[nw][nT][k],f[pw][T][k]);
                        if(g[j][i]=='*')
                        {
                            nT|=1<<(j-1);int pls=0;
                            if(j>1&&!(T&(1<<(j-2))))++pls;
                            if(i>1&&!(T&(1<<(j-1))))++pls;
                            if(i<m)++pls;if(j<n)++pls;
                            add(f[nw][nT][k+pls],MOD-f[pw][T][k]);
                        }
                    }
        }
    for(int T=0;T<S;++T)
        for(int i=1;i<=sum;++i)
            add(ans,1ll*f[nw][T][i]*inv[i]%MOD);
    ans=1ll*ans*sum%MOD;printf("%d\n",ans);
    return 0;
}