博客园 首页 私信博主 显示目录 隐藏目录 管理 动画

一双木棋——状压dp

题目大意

给定一个\(n \times m\)的棋盘,每个格子有两个权值\(a,b\),A想最大化分差B最小化,A先出手。某格子可以下棋当且仅当其上方全部被下满并且左边全部被下满,请问下满整个棋盘的分数是多少?

\(n,m \leq 10\)

思路

模拟下的过程,发现它图形永远都是要给上三角形。用二进制来维护它的轮廓进行dp。

\(f[S]\)表示按照规则的当前得分,转移的时候找“拐角”(01/10),在拐角处进行操作维护。

代码

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 11;
const int INF = 0x3f3f3f3f;

int n,m;
int a[N][N];
int b[N][N];
int f[1 << (2 * N - 1)];
bool vis[1 << (2 * N - 1)];

int dp (int sta,int x) {
	if(sta == ((1 << n) - 1) << m) return 0;
	if(vis[sta]) return f[sta];
	vis[sta] = 1;
	f[sta] = x ? -INF : INF;
	int xx = n;
	int yy = 0;
	for(int i = 0;i < (n + m - 1);i ++) {
		if((sta >> i) & 1) {
			-- xx;
		}else ++ yy;
		if(((sta >> i) & 3) != 1) continue;//不是拐角
		int new_sta = sta ^ (3 << i);
		if(x) {
			f[sta] = max(f[sta],dp(new_sta,x ^ 1) + a[xx][yy]);
		}
		else f[sta] = min(f[sta],dp(new_sta,x ^ 1) - b[xx][yy]);
	}
	return f[sta];
}
int main () {
	cin >> n >> m;
	for(int i = 0;i < n; i ++) {
		for(int j = 0;j < m; j ++) {
			cin >> a[i][j];
		}
	}
	for(int i = 0;i < n; i ++) {
		for(int j = 0;j < m; j ++) {
			cin >> b[i][j];
		}
	}
	int sta = (1 << n) - 1;
	cout << dp(sta,1) << endl;
	return 0;
}
posted @ 2022-02-13 23:51  Allorkiya  阅读(20)  评论(0编辑  收藏  举报