HDU 4539 鄭廠長系列故事——排兵佈陣 (狀壓思維題)*
阿新 • • 發佈:2018-12-11
#include<bits/stdc++.h> using namespace std; #define debug puts("YES"); #define rep(x,y,z) for(int (x)=(y);(x)<(z);(x)++) #define read(x,y) scanf("%d%d",&x,&y) #define ll long long #define lrt int l,int r,int rt #define lson l,mid,rt<<1 #define rson mid+1,r,rt<<1|1 #define root l,r,rt const int maxn =1e6+5; const int mod=1e9+7; const int ub=1e6; ll powmod(ll x,ll y){ll t; for(t=1;y;y>>=1,x=x*x%mod) if(y&1) t=t*x%mod; return t;} ll gcd(ll x,ll y){return y?gcd(y,x%y):x;} int n,m,x,row[110];///每一行的狀態 int s[1<<11],cnt[1<<11];///合法狀態和可以排布的士兵數量 int dp[110][220][220]; int getcnt(int x){int ret=0;while(x>0) x-=(x&(-x)),ret++;return ret;} /* 中文題目。 狀態壓縮, 先找出狀態轉移方程, dp[i][j][k]=max(dp[i-1][k][p]): 列舉p。 代表在第i行狀態為j,第i-1行狀態為k時的最大值。 因為我們可以看出當相距三行及以上時狀態是獨立的, 所以我們需要用DP暴力列舉當前狀態的前兩行。 那麼下面就是狀態壓縮和位運算技巧了。 首先為了判定方便要把0壓縮成1,因為我們答案要列舉的是 不含零的子集,所以把0壓成1就可以用與運算直接判斷是否符合要求。 我們對於每行先把符合要求的狀態壓出來,然後直接在符合要求的狀態中進行列舉, 符合的條件是相鄰間距不能有剛好為2的。 四層迴圈,後三層每一層都有條件,第二層是列舉當前row的1的子集即可, 第三層是i層與i-1層符合要求,就是不能有剛好錯位的。 第四層是要求不能有和i層狀態重複的,這很容易判得(不用麻煩的判定和i-1層的關係) 這也是狀態轉移的技巧吧。 */ int main() { while(~scanf("%d%d",&n,&m)) { for(int i=0;i<n;i++) { row[i]=0; for(int j=0;j<m;j++) { scanf("%d",&x); if(!x) row[i]=(row[i]<<1)|1;///把狀態反過來這樣方便點 else row[i]<<=1; } } int idx=0,ans=0; for(int i=0;i<(1<<m);i++) { if(i&(i<<2)) continue; s[idx]=i,cnt[idx++]=getcnt(i); } memset(dp,0,sizeof(dp));///初始化為0還是必要的 for(int i=0;i<idx;i++) { if(s[i]&row[0]) continue; dp[0][i][0]=cnt[i]; }///初始化DP狀態陣列 for(int i=1;i<n;i++) { for(int j=0;j<idx;j++) { if(s[j]&row[i]) continue;///必須是完全符合的才行 for(int k=0;k<idx;k++) { if( (s[j]&(s[k]>>1)) || (s[j]&(s[k]<<1)) ) continue;/// for(int p=0;p<idx;p++) { if( s[j]&s[p] ) continue;///有一個相同位都不行 dp[i][j][k]=max(dp[i][j][k],dp[i-1][k][p]+cnt[j]);///狀態轉移方程 } } } } for(int i=0;i<idx;i++) for(int j=0;j<idx;j++) ans=max(ans,dp[n-1][i][j]); printf("%d\n",ans); } return 0; }