【luogu P4363】[九省聯考 2018] 一雙木棋 chess(狀壓DP)
阿新 • • 發佈:2022-03-18
[九省聯考 2018] 一雙木棋 chess
題目連結:luogu P4363
題目大意
給你一個棋盤,然後每個格子不同人選分別有不同的分數。
然後兩個人輪流選,選一個數的時候必須保證它左上角的數都選了。
然後在兩個人都希望自己分數和最大的情況下,求它們的得分差。
思路
一道純純狀壓題。
(好吧還是有點難調的,不過應該是我寫的醜)
畢竟你 \(n,m\leqslant 10\),而且你會發現你每個時刻的狀態可以表示為一條從 \((0,0)\) 到 \((n,m)\),每次可以選橫座標或縱座標加一的線。
那我們可以用一個長度為 \(n+m\) 的 \(01\) 串表示(這裡 \(0\) 表示向上,\(1\)
然後搞就可以了,要記得的是要倒過來做。
(畢竟你這個是兩個人意圖不同的不倒過來做是不行噠!)
(本人親測正過來然後 \(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; }