jzoj5990. 【北大2019冬令營模擬2019.1.6】Bear (狀壓dp)
阿新 • • 發佈:2019-01-07
題面
題解
我永遠討厭dp.jpg
搞了一個下午優化複雜度最後發現只要有一個小trick就可以A了→_→。全場都插頭dp就我一個狀壓跑得賊慢……
不難發現我們可以狀壓,對於每一行,用狀態\(S\)表示有哪些格子是已經被上一行推倒了的,那麼我們可以列舉本行所有格子的字母情況,然後計算一下這個時候下一行格子被推倒的情況,把這一行的貢獻加到下一行就行了。
簡單來說就是記一個\(f[pos][S]\)表示第\(pos\)行,格子被推倒的情況為\(S\)時的方案數,\(dp[pos][S]\)為所有方案中推倒樹的總數,那麼假設一個選字母的方案會使下一行的推倒情況為\(S'\),會使這一行可以推倒\(k\)
\[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));
}