一双木棋 chess
题意
\(n \times m, (n, m\leq 10)\) 的矩阵,每次可以在左上都有棋子的地方下,奇数次下在 \((i, j)\) 的价值是 \(a_{i, j}\),偶数次是 \(-b_{i, j}\)。
奇数次时想让价值和最大,偶数次时想让价值和最小。
问最终结果。
轮廓线dp,状压dp
状态
如果第 \(i\) 行放了 \(c_i\) 个,那么它一定左到右连续的。
并且从第 \(1\) 行到第 \(n\) 行,\(c_i\) 是递减的。
状态就可以用轮廓线唯一表示。
用 \(0\) 表示 \(m\) 条横向的线,用 \(1\) 表示 \(n\) 条竖向的线。
令 \(f[S]\) 表示棋盘状态 \(S\) 到全部下完的价值。
初始和最终状态
由于转移是从最终倒回来的。
初始状态 \(f[111\cdots 0000\cdots] = 0\), 表示全部下完离全部下完的价值和是 0。
其它全部设为无穷大或无穷小。
最终状态 \(f[0000\cdots 111\cdots]\) 表示初始状态离全部下完的价值。
转移
能发现选一格,实际上的轮廓线就是把 \(1\) 和 下一位的 \(0\) 交换。
选的位置可以用轮廓线状态算出来,设为 \((x, y)\)。
所以就有:
1.偶数次时
\[f[S] = \max_{S_i = 1, S_{i + 1} = 0} \{f[S \oplus (3 << i)] + a[x][y] \}
\]
2.奇数次时
\[f[S] = \min_{S_i = 1, S_{i + 1} = 0} \{f[S \oplus (3 << i)] - b[x][y] \}
\]
分析
状态是 \(O(2^{n + m})\) 的,转移是 \(O(n + m)\)。
最终是 \(O(2^{n + m}(n + m))\)
代码
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int MAXN = 15;
const int INF = 0x3f3f3f3f;
//const int mod = 1000000007;
int mod;
const double eps = 1e-9;
template <typename T>
void Read(T &x) {
x = 0; T f = 1; char a = getchar();
for(; a < '0' || '9' < a; a = getchar()) if (a == '-') f = -f;
for(; '0' <= a && a <= '9'; a = getchar()) x = (x * 10) + (a ^ 48);
x *= f;
}
inline int add(const int &a, const int &b) {
static int c;
c = a + b;
if (c >= mod) c -= mod;
if (c < 0) c += mod;
return c;
}
inline int mul(const int &a, const int &b) {
return 1ll * a * b % mod;
}
int qpow(int a, int b) {
int sum(1);
while(b) {
if (b & 1) sum = mul(sum, a);
a = mul(a, a);
b >>= 1;
}
return sum;
}
int n, m;
int a[MAXN][MAXN], b[MAXN][MAXN];
int f[1 << 21];
bool vis[1 << 21];
int dfs(int S, int N) {
if (vis[S]) return f[S];
vis[S] = 1;
f[S] = N ? - INF : INF;
int x = n, y = 0;
for (int i = 0; i < n + m - 1; i ++) {
if (S >> i & 1) x --;
else y ++;
if ((S >> i & 3) != 1) continue;
int to = S ^ (3 << i);
if (N) f[S] = max(f[S], dfs(to, N ^ 1) + a[x + 1][y + 1]);
else f[S] = min(f[S], dfs(to, N ^ 1) - b[x + 1][y + 1]);
}
return f[S];
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= m; j ++)
cin >> a[i][j];
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= m; j ++)
cin >> b[i][j];
memset(f, 0x3f, sizeof(f));
f[((1 << n) - 1) << m] = 0;
vis[((1 << n) - 1) << m] = 1;
cout << dfs((1 << n) - 1, 1);
return 0;
}