【LuoguP4363】一双木棋【状压DP】

链接

题目大意:
一个n*m的网格,每个格子有a,b两个元素。两个人执黑白棋轮流下,要求每次落子点左和上必须有棋子。最后黑棋的总和是黑棋落子点的a总和,白棋为b总和,双方都采取最大化自己与对手得分差距的策略,求最后的得分差距。

思路:

n,m只有10,直接考虑状压。
这种斜角堆叠的网格很容易使用轮廓线,一开始设m个0,在当前行有多少个棋子数就从右到左数多少个0将一个1插入在后面。
然后考虑状态转移,就是将一个状态的01变成10得到前驱状态,然后用记搜。
然后以后的状压(轮廓线)不要用映射的方法了,这会导致拓扑序和顺序混乱。

code

#include<iostream>
#include<cstdio>
#include<cstring>

using namespace std;

const int MAXN = 21, inf = 1e9;

int n, m;
int a[MAXN][MAXN], b[MAXN][MAXN];
int f[1 << MAXN];

int dfs(int i, bool flag) {
	if(f[i] != -1044266559)
        return f[i];
	if(flag) f[i] = -inf;
    else f[i] = inf;
	int x = n + 1, y = 1;
	for (int p = 0; p < n + m - 1; p ++) {
		if(i >> p & 1) x --;
        else y ++;
		if((i >> p & 3) != 1) continue;
		int j = i ^ 3 << p;
		if(flag)
			f[i] = max(f[i], dfs(j, flag ^ 1) + a[x][y]);
		else
			f[i] = min(f[i], dfs(j, flag ^ 1) - b[x][y]);
	}
	return f[i];
}

int main() {
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; i ++)
        for(int j = 1; j <= m; j ++)
			scanf("%d", &a[i][j]);
	for(int i = 1; i <= n; i ++)
        for(int j = 1; j <= m; j ++)
			scanf("%d", &b[i][j]);
	memset(f, -0x3f, sizeof(f));
	f[((1 << n) - 1) << m] = 0;
	printf("%d", dfs((1 << n) - 1, 1));
    return 0;
}

posted @ 2026-08-11 20:05  VLengu  阅读(3)  评论(0)    收藏  举报