1. 程式人生 > 其它 >【luogu P4363】[九省聯考 2018] 一雙木棋 chess(狀壓DP)

【luogu P4363】[九省聯考 2018] 一雙木棋 chess(狀壓DP)

[九省聯考 2018] 一雙木棋 chess

題目連結:luogu P4363

題目大意

給你一個棋盤,然後每個格子不同人選分別有不同的分數。
然後兩個人輪流選,選一個數的時候必須保證它左上角的數都選了。
然後在兩個人都希望自己分數和最大的情況下,求它們的得分差。

思路

一道純純狀壓題。
(好吧還是有點難調的,不過應該是我寫的醜)

畢竟你 \(n,m\leqslant 10\),而且你會發現你每個時刻的狀態可以表示為一條從 \((0,0)\)\((n,m)\),每次可以選橫座標或縱座標加一的線。
那我們可以用一個長度為 \(n+m\)\(01\) 串表示(這裡 \(0\) 表示向上,\(1\)

表示向右),然後你會發現可以直接 \(2^{n+m}*(n+m)\) 狀壓 DP。

然後搞就可以了,要記得的是要倒過來做。
(畢竟你這個是兩個人意圖不同的不倒過來做是不行噠!)
(本人親測正過來然後 \(min/max\) 反過來還有 \(30\),反過來是因為不反過來樣例都過不了awa)

程式碼

#include<cstdio>
#include<iostream>
#include<algorithm>
#define INF 0x3f3f3f3f3f3f3f3f 

using namespace std;

const int N = 10 + 1;
int n, m, a[N][N], b[N][N];
int f[1 << 20], num[1 << 20], to;
int xl[1 << 20], cnt[1 << 20][21];

bool cmp(int x, int y) {
	return num[x] > num[y];
}

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]);
	
	for (int i = 0; i < (1 << (n + m)); i++) {
		xl[i] = i;
		int lnum = n;
		for (int j = 1; j <= n + m; j++)
			if ((i >> (j - 1)) & 1) num[i] += lnum;
				else {
					lnum--; if (lnum < 0) num[i] = 2e9;
				}
		if (lnum != 0) num[i] = 2e9;
		if (num[i] != 2e9) {//這裡是把一些沒用的狀態給直接壓掉了
			for (int j = 1; j <= n + m; j++)
				cnt[i][j] = cnt[i][j - 1] + ((i >> (j - 1)) & 1);
			if (num[i] & 1) f[i] = INF;
				else f[i] = -INF;
			if (num[i] == n * m) f[i] = 0;
		}
	}
	sort(xl + 0, xl + (1 << (n + m)), cmp);
	
	for (int i = 0; i < (1 << (n + m)); i++) {
		if (num[xl[i]] == 2e9) continue;
		if (num[xl[i]] & 1) {//max
			for (int j = 1; j < (n + m); j++)
				if (((xl[i] >> (j - 1)) & 1) && !((xl[i] >> j) & 1)) {
					to = xl[i] ^ (1 << (j - 1)) ^ (1 << j);
					f[to] = max(f[to], f[xl[i]] + a[n - (j + 1 - cnt[xl[i]][j + 1]) + 1][cnt[xl[i]][j + 1]]);
				}
		}
		else {//min
			for (int j = 1; j < (n + m); j++)
				if (((xl[i] >> (j - 1)) & 1) && !((xl[i] >> j) & 1)) {
					to = xl[i] ^ (1 << (j - 1)) ^ (1 << j);
					f[to] = min(f[to], f[xl[i]] - b[n - (j + 1 - cnt[xl[i]][j + 1]) + 1][cnt[xl[i]][j + 1]]);
				}
		} 
	}
	
	printf("%d", f[((1 << m) - 1) << n]);
	
	return 0;
}