[2019CCPC秦皇島] G Game on Chessboard 狀壓dp

給出一個最大12×1212×12的棋盤,然後棋盤上有黑色或者白色的棋子。每個棋子都有一個權值。現在你要把所有的棋子全部拿走,你有兩種取法:1.任意取走一個棋子,付出這個棋子權值的代價。2.取走一對棋子,付出兩個棋子權值差絕對值的代價,但是必須要滿足的條件是你如果想拿走現在這個棋子,你必須要保證這個棋子作爲右上端點的矩形裏面沒有其它的棋子。求問拿完所有的棋子最小的權值是多少。
首先,單取一個棋子必定劣於取一對棋子,所以只有無法取對的時候才考慮單取。其次,由於數據規模非常小,把取走棋子的狀態壓縮爲一條輪廓線,顯然合法的輪廓線只有C2nnC_{2n}^{n}條,並且與下方取走哪些棋子一一對應,只需要一個2n2n0101串表示從起點出發00表示向右走,11表示向下走。最後,對於當前的這個狀態,在每個0101的位置上可以選擇取走這個位置上方的一個棋子。
轉移分兩種情況:單取一個合法的/暴力枚舉一對合法的。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define MIN(x,y) (x)<(y)?(x):(y) 
const int inf=0x3f3f3f3f;
const int N=(1<<24);
int a[12][12];
char ch[12][12];
int f[N]; 
inline int get(int S,int bit) { return (S>>bit)&1; }
int main() {
	int n;
	while(scanf("%d",&n)!=EOF) {
		for(int i=0;i<n;i++)
			scanf("%s",ch[i]);
		for(int i=0;i<n;i++) {
			for(int j=0;j<n;j++) {
				scanf("%d",&a[i][j]);  
			}
		}	
		int e=(1<<n)-1,s=e<<n; // right down // down right    
		fill(f+e,f+s,1e9);
		f[s]=0;
		for(int S=s;S>e;S--) {
			if(__builtin_popcount(S)!=n) continue;
			int x=n-1,y=n;
			for(int i=0;i+1<2*n;i++) {
				if(get(S,i)) x--;
				else y--;
				if(!get(S,i)&&get(S,i+1))  { 
		 			f[S-(1<<i)]=MIN(f[S-(1<<i)],f[S]+(ch[x][y]!='.')*a[x][y]);	
					int nx=n-1,ny=n;
					for(int j=0;j+1<i;j++) {
						if(get(S,j)) nx--;
						else ny--;
						if(!get(S,j)&&get(S,j+1)&&ch[x][y]+ch[nx][ny]=='B'+'W') 
							f[S-(1<<i)-(1<<j)]=MIN(f[S-(1<<i)-(1<<j)],f[S]+abs(a[x][y]-a[nx][ny]));
					}
				} 
			}
		}
		printf("%d\n",f[e]);	
	}
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章