NOI2001炮兵陣地
題目傳送門
PS:本道題目建議在對狀壓dp有一定了解的基礎下學習,如有不懂可以先去學一下狀壓dp入門
題目大意:給你n*m個格子,有些格子可以用來部署軍隊,用P表示,有些則不能,用H表示,如果在一個格子上部署了軍隊,則上下左右各2個格子都不能部署軍隊,也就是呈十字架狀,看到數據範圍(n<=100,m<=10)很容易想到使用狀壓dp,因為m列數最大只有10,我們可以壓縮每一行的狀態,最大只有(1<<10)-1種狀態,但是由於這一行的狀態對下一行以及下兩行都有影響,我們需要一個三維數組來保存狀態,dp[i][j][k],代表第i行狀態為j,上一行狀態為k最大能部署的軍隊數,那麽我們應該就能推出狀態轉移方程:
dp[i][j][k]=max(dp[i][j][k],dp[i-1][k][l]+num[i])(l為上兩行的狀態,num[i]為狀態為i時能部署的軍隊數)
但是有一個問題,就是n最大為10,但是我們的數組好像還是開不下,dp[110][1<<10][1<10]顯然是超內存的,怎麽辦呢?我們其實可以抓住如果在一個格子上部署軍隊則左右兩邊各2個格子都不能部署軍隊這一點來預處理進而達到縮小內存的方法,那麽怎麽預處理呢,其實和那道入門題的判斷方法是一樣的,我們將某一個狀態分別右移1位,2位,這樣便得到三個狀態,然後兩兩相與,如果都等於0說明這個狀態是合法的,那麽這樣下來最多的狀態數也只有60個,這樣數組就足夠用了
具體實現方法如下
1 void init() 2 { 3 for(int i=0;i<(1<<m);i++) 4 { 5 if(!(i&(i>>1)) && !(i&(i>>2)) && !((i>>1)&(i>>2))) 6 { 7 can[++cnt]=i; 8 num[cnt]=get(i); 9 } 10 } 11 //cout<<cnt<<endl;-------算出來最大只有6012 }
然後就是保存每一行是否可以部署軍隊的操作,這裏記得要將P和H反過來存,若為正著存,則後面不部署軍隊的情況是考慮不到的,應該很好理解吧
1 in(n);in(m); 2 for(int i=1;i<=n;i++) 3 { 4 for(int j=1;j<=m;j++) 5 { 6 char ch; 7 cin>>ch; 8 if(ch==‘H‘) cur[i]|=1<<(j-1);//記得反過來存 9 } 10 }
然後就是dp部分了,因為從第三行開始,後面的行的狀態都與前面一行及兩行的狀態有關,因此我們先處理出第一行以及第二行的狀態,以便後面的遞推
第一行的時候我們是不需要判斷前面的行數的,因此只需要判斷這一行的狀態是否和合法就行了
而第二行的時候我們則需要根據在第二行狀態合法的前提下還需要根據第一行的狀態看有沒有沖突的的情況,這種情況是要舍去的,其他的就沒什麽好講的了
1 for(int i=1;i<=cnt;i++)//不用判斷上一行 2 { 3 if(!(cur[1]&can[i])) 4 { 5 dp[1][i][0]=max(dp[1][i][0],num[i]); 6 tot=max(tot,dp[1][i][0]); 7 } 8 } 9 //處理第一行 10 for(int i=1;i<=cnt;i++)//只用判斷上一行 11 { 12 if(!(cur[2]&can[i])) 13 { 14 for(int j=1;j<=cnt;j++) 15 { 16 if(!(cur[1]&can[j]) && !(can[i]&can[j])) 17 { 18 dp[2][i][j]=max(dp[2][i][j],dp[1][j][0]+num[i]); 19 tot=max(tot,dp[2][i][j]); 20 } 21 } 22 } 23 } 24 //處理第二行
其實後面的行數跟前面的判斷差不多,就是先判斷本行再判斷上一行,再判斷上兩行,最判斷這三個狀態有沒有沖突,只要有一個存在沖突,這種狀態就是不合法的,那麽我們就把它舍去
最後上個完整版代碼
1 #include<iostream> 2 #include<cstdio> 3 #include<cmath> 4 #include<string> 5 #include<cstring> 6 #define in(i) (i=read()) 7 using namespace std; 8 int read() 9 { 10 int ans=0,f=1; 11 char i=getchar(); 12 while(i<‘0‘||i>‘9‘) 13 { 14 if(i==‘-‘) f=-1; 15 i=getchar(); 16 } 17 while(i>=‘0‘&&i<=‘9‘) 18 { 19 ans=(ans<<1)+(ans<<3)+i-‘0‘; 20 i=getchar(); 21 } 22 return ans*f; 23 } 24 int dp[110][65][65]; 25 int can[65]; 26 int cur[110]; 27 int num[65]; 28 int n,m,cnt,tot; 29 int get(int x) 30 { 31 int ans=0; 32 for(int i=1;i<=m;i++) 33 { 34 if((x&(1<<(i-1)))!=0) ans++;//判斷是否可以部署軍隊,若可以則ans++ 35 } 36 return ans; 37 } 38 void init() 39 { 40 for(int i=0;i<(1<<m);i++) 41 { 42 if(!(i&(i>>1)) && !(i&(i>>2)) && !((i>>1)&(i>>2)))//相鄰之間沒有1 43 { 44 can[++cnt]=i; 45 num[cnt]=get(i); 46 } 47 } 48 //cout<<cnt<<endl;-------算出來最大只有60 49 } 50 int main() 51 { 52 in(n);in(m); 53 for(int i=1;i<=n;i++) 54 { 55 for(int j=1;j<=m;j++) 56 { 57 char ch; 58 cin>>ch; 59 if(ch==‘H‘) cur[i]|=1<<(j-1);//記得反過來存 60 } 61 } 62 init();//預處理 63 for(int i=1;i<=cnt;i++)//不用判斷上一行 64 { 65 if(!(cur[1]&can[i])) 66 { 67 dp[1][i][0]=max(dp[1][i][0],num[i]); 68 tot=max(tot,dp[1][i][0]); 69 } 70 } 71 //處理第一行 72 for(int i=1;i<=cnt;i++)//只用判斷上一行 73 { 74 if(!(cur[2]&can[i])) 75 { 76 for(int j=1;j<=cnt;j++) 77 { 78 if(!(cur[1]&can[j]) && !(can[i]&can[j])) 79 { 80 dp[2][i][j]=max(dp[2][i][j],dp[1][j][0]+num[i]); 81 tot=max(tot,dp[2][i][j]); 82 } 83 } 84 } 85 } 86 //處理第二行 87 for(int i=3;i<=n;i++)//枚舉行數,對後n-2行進行處理 88 { 89 for(int j=1;j<=cnt;j++)//枚舉第i行的狀態 90 { 91 if(!(cur[i]&can[j]))//判斷本行是否有沖突 92 { 93 for(int k=1;k<=cnt;k++)//若無則枚舉下一行的狀態 94 { 95 if(!(cur[i-1]&can[k]))//判斷上一行狀態是否有沖突 96 { 97 for(int l=1;l<=cnt;l++)//枚舉上上一行狀態 98 { 99 if(!(cur[i-2]&can[l])) 100 { 101 if(!(can[j]&can[k]) && !(can[j]&can[l]) && !(can[k]&can[l]))//若都無沖突 102 { 103 dp[i][j][k]=max(dp[i][j][k],dp[i-1][k][l]+num[j]);//狀態轉移 104 tot=max(tot,dp[i][j][k]); 105 } 106 } 107 } 108 } 109 } 110 } 111 } 112 } 113 cout<<tot<<endl; 114 return 0; 115 }
NOI2001炮兵陣地