1. 程式人生 > >NOI2001 炮兵陣地

NOI2001 炮兵陣地

def https shape code str targe namespace amp 前行

傳送門

這道題看數據範圍n<=10可以很快的想出是狀壓DP。

之後最暴力的方法也是可以想到的,就是直接暴力枚舉當前行,上一行,上上行的情況(因為炮兵能打兩行),直接暴力DP。

不過這樣一來會T,二來會MLE。

那我們怎麽辦?我們註意到一個炮兵能打到左右兩格,說明在一行之內有很多情況都是不可行的,根本不用枚舉。我們可以直接先行預處理出所有可行的情況,方法就是暴力枚舉1~1<<m-1,對於每個i,判斷其&i<<2,i<<1,i>>1,i>>2即可。(這個在互不侵犯那道題中都是老套操作了)

這樣就處理出了每行所有可行的情況,我們驚奇的發現其實最多有60種……

之後就可以像剛才一樣暴力的狀壓DP了。

只要預處理之後,記錄一下每行的地形,繼續用按位與的方法判斷當前情況是否可行,之後求解即可。然後,註意在判斷三行的情況的時候要分別判斷每個兩行是否可行,一起判斷由於中間按位與可能為0,會出現錯誤。

註意第一行和第二行要單獨處理。

看一下代碼。

#include<cstdio>
#include<algorithm>
#include<cstring>
//#include<iostream>
#include<cmath>
#include<queue>
#include<set>
#define
rep(i,a,n) for(int i = a;i <= n;i++) #define per(i,n,a) for(int i = n;i >= a;i--) #define enter putchar(‘\n‘) using namespace std; typedef long long ll; const int M = 205; int read() { int ans = 0,op = 1; char ch = getchar(); while(ch < 0 || ch > 9) { if(ch == -
) op = -1; ch = getchar(); } while(ch >= 0 && ch <= 9) { ans *= 10; ans += ch - 0; ch = getchar(); } return ans * op; } int n,m,shape[105],s[205],dp[105][205][205],sum[205],k,ans; char g[105][15]; int getsum(int x) { int cur = 0; while(x) { cur += (x&1); x >>= 1; } return cur; } void init1() { rep(i,0,(1<<m)-1) { if((!(i&(i<<1))) && (!(i&(i<<2))) && (!(i&(i>>1))) && (!(i&(i>>2)))) { s[++k] = i; sum[k] = getsum(i); if(!(i&shape[1])) dp[1][0][k] = sum[k]; } } } void init2() { rep(i,1,k) rep(j,1,k) { if((!(s[i]&s[j])) && (!(s[j]&shape[2]))) dp[2][i][j] = max(dp[2][i][j],dp[1][0][i] + sum[j]); } } int main() { n = read(),m = read(); rep(i,1,n) { scanf("%s",g[i]); rep(j,0,m-1) if(g[i][j] == H) shape[i] |= (1 << j); } init1(),init2(); rep(i,3,n) rep(j,1,k) { if(!(s[j] & shape[i])) { rep(p,1,k) { if(!(s[j] & s[p])) rep(q,1,k) { if((!(s[q] & s[p])) && (!(s[q] & s[j]))) dp[i][p][j] = max(dp[i][p][j],dp[i-1][q][p] + sum[j]); } } } } rep(i,1,k) rep(j,1,k) ans = max(ans,dp[n][i][j]); printf("%d\n",ans); return 0; }

NOI2001 炮兵陣地