1. 程式人生 > >jzoj5990. 【北大2019冬令營模擬2019.1.6】Bear (狀壓dp)

jzoj5990. 【北大2019冬令營模擬2019.1.6】Bear (狀壓dp)

題面


題解

我永遠討厭dp.jpg

搞了一個下午優化複雜度最後發現只要有一個小trick就可以A了→_→。全場都插頭dp就我一個狀壓跑得賊慢……

不難發現我們可以狀壓,對於每一行,用狀態\(S\)表示有哪些格子是已經被上一行推倒了的,那麼我們可以列舉本行所有格子的字母情況,然後計算一下這個時候下一行格子被推倒的情況,把這一行的貢獻加到下一行就行了。

簡單來說就是記一個\(f[pos][S]\)表示第\(pos\)行,格子被推倒的情況為\(S\)時的方案數,\(dp[pos][S]\)為所有方案中推倒樹的總數,那麼假設一個選字母的方案會使下一行的推倒情況為\(S'\),會使這一行可以推倒\(k\)

棵樹,則有轉移\[f[pos+1][S']+=f[pos][S]\]
\[dp[pos+1][S']+=f[pos][S]+k\times f[pos][S]\]
最後\(f[n+1][0]\)就是答案。這樣的話能有\(40\)分(建議先看一下40分程式碼不然看不太懂AC程式碼的……)

//minamoto
#include<bits/stdc++.h>
#define R register
#define fp(i,a,b) for(R int i=a,I=b+1;i<I;++i)
#define fd(i,a,b) for(R int i=a,I=b-1;i>I;--i)
#define go(u) for(int i=head[u],v=e[i].v;i;i=e[i].nx,v=e[i].v)
using namespace std;
const int N=13,M=35,L=(1<<21)+5;
int a[N][M],f[N][L],dp[N][L],g[N][L],n,m,P,lim,ans,vis[N];
inline int add(R int x,R int y){return x+y>=P?x+y-P:x+y;}
inline int mul(R int x,R int y){return 1ll*x*y-1ll*x*y/P*P;}
void solve(int pos){
    fp(i,0,lim-1)if(f[pos][i]){
        fp(j,0,lim-1){
            int S=0,res=0;
            fp(k,0,m-1)vis[k]=i&(1<<k);
            fp(k,0,m-1)if(!vis[k]){
                if(j&(1<<k)){
                    if(k!=m-1&&!vis[k+1])vis[k]=vis[k+1]=1,++res;
                    else{
                        if(pos!=n)S|=(1<<k),++res;
                    }
                }else{
                    if(pos!=n)S|=(1<<k),++res;
                    else if(k!=m-1&&!vis[k+1])vis[k]=vis[k+1]=1,++res;
                }
            }
            f[pos+1][S]=add(f[pos+1][S],f[pos][i]);
            dp[pos+1][S]=add(dp[pos+1][S],mul(res,f[pos][i]));
            dp[pos+1][S]=add(dp[pos+1][S],dp[pos][i]);
        }
    }
}
int main(){
//  freopen("testdata.in","r",stdin);
    freopen("bear.in","r",stdin);
    freopen("bear.out","w",stdout);
    scanf("%d%d%d",&n,&m,&P),lim=(1<<m);
    f[1][0]=1,dp[1][0]=0;
    fp(i,1,n)solve(i);
    printf("%d\n",dp[n+1][0]);
}

然後我們發現複雜度高的主要原因是因為行數太多,不過列數很少,那麼我們可以對列進行狀壓。然而這樣的話會不符合推倒的順序。

我們考慮每一條副對角線,這條副對角線上肯定是從右上到左下的推倒順序,於是我們可以對每一條副對角線進行狀壓,因為副對角線上元素個數為\(min(n,m)\),所以時間複雜度沒問題

信心滿滿的交上去結果只有\(70\)分,因為按上面那種方式列舉行的推倒情況和行的字母不太好,對於那些已經被推倒的格子,它們不管怎麼選都沒有影響,所以我們可以只列舉那些沒有被推倒的格子,那些已經被推倒的格子直接把貢獻加上去就可以了,這樣的話複雜度就是\(O(3^n\times\)亂七八糟的常數\()\)

還是一句話,注意細節

//minamoto
#include<bits/stdc++.h>
#define R register
#define fp(i,a,b) for(R int i=a,I=b+1;i<I;++i)
#define fd(i,a,b) for(R int i=a,I=b-1;i>I;--i)
#define go(u) for(int i=head[u],v=e[i].v;i;i=e[i].nx,v=e[i].v)
using namespace std;
const int N=55,M=35,L=(1<<12)+5;
int a[N][M],f[N][L],dp[N][L],n,m,P,ans,vis[N];
int id[N][M],sz[L],bin[N];
inline int add(R int x,R int y){return x+y>=P?x+y-P:x+y;}
inline int mul(R int x,R int y){return 1ll*x*y-1ll*x*y/P*P;}
void solve(int pos){
    int cnt=pos-max(0,pos-n)-max(0,pos-m);
    int stx,sty,edx,edy,dx,dy;
    if(pos<=m)stx=pos,sty=1;
        else stx=m,sty=pos-m+1;
    if(pos<=n)edx=1,edy=pos;
        else edx=pos-n+1,edy=n;
    int qaq=pos+1>m,c=pos+1-max(0,pos+1-n)-max(0,pos+1-m);
    int lim=(1<<cnt)-1;
    fp(i,0,(1<<cnt)-1)if(f[pos][i]){
        int T=lim^i,p=bin[sz[i]],flag=-2;
        for(R int j=T;flag+=(j==T);j=(j-1)&T){
            int res=0,S=0;
            fp(k,0,c-1)vis[k]=0;
            dx=stx,dy=sty;
            fp(k,0,cnt-1){
                if(!(i&(1<<k))){
                    if(j&(1<<k)){
                        if(dx!=m&&!vis[k-qaq])vis[k-qaq]=1,++res,S|=(1<<(k-qaq));
                        else if(dy!=n)vis[k+1-qaq]=1,++res,S|=(1<<(k-qaq+1));
                    }else{
                        if(dy!=n)vis[k+1-qaq]=1,++res,S|=(1<<(k-qaq+1));
                        else if(dx!=m&&!vis[k-qaq])vis[k-qaq]=1,++res,S|=(1<<(k-qaq));
                    }
                }--dx,++dy;
            }
            f[pos+1][S]=add(f[pos+1][S],mul(f[pos][i],p));
            dp[pos+1][S]=add(dp[pos+1][S],mul(mul(f[pos][i],res),p));
            dp[pos+1][S]=add(dp[pos+1][S],mul(dp[pos][i],p));
        }
    }
}
int main(){
//  freopen("testdata.in","r",stdin);
    freopen("bear.in","r",stdin);
    freopen("bear.out","w",stdout);
    scanf("%d%d%d",&n,&m,&P);
    fp(i,1,(1<<(min(n,m)))-1)sz[i]=sz[i>>1]+(i&1);
    bin[0]=1;fp(i,1,30)bin[i]=mul(bin[i-1],2);
    f[1][0]=1,dp[1][0]=0;
    fp(i,1,n+m-2)solve(i);
    printf("%d\n",mul(add(dp[n+m-1][0],dp[n+m-1][1]),2));
}