排成一条线的纸牌博弈问题
题目描述
给定一个整型数组arr,代表数值不同的纸牌排成一条线,玩家A和玩家B依次拿走每张纸牌,规定玩家A先拿,玩家B后拿,但是每个玩家每次只能拿走最左和最右的纸牌,玩家A和玩家B绝顶聪明。请返回最后的获胜者的分数。
输入描述:
输出包括两行,第一行一个整数 n ( 1 ≤ n ≤ 5000 ) n(1 \leq n \leq 5000 ) n(1≤n≤5000),代表数组arr长度,第二行包含n个整数,第i个代表 a r r [ i ] ( 1 ≤ a r r [ i ] ≤ 1 0 5 ) arr[i]( 1 \leq arr[i] \leq 10^5 ) arr[i](1≤arr[i]≤105)。
输出描述:
输出一个整数,代表最后获胜者的分数。
示例1
输入
4
1 2 100 4
输出
101
备注:
时间复杂读 O ( n 2 ) O(n^2) O(n2),空间复杂度 O ( n 2 ) O(n^2) O(n2)
题解:
设胜者的总分为 x ,败者的总分为 y,并且 x - y = diff ---- 式(1);
又两者的分数和为纸牌的总分,有 x + y = sum ---- 式(2);
式(1) 和 式(2) 联立得到 x = (sum + diff) / 2。
此题就是求最大的 diff 。
设 F[i, j] 表示 在 i-j 牌中,先拿的分数减去后拿的分数,故 F[i, j] = max{a[i] - F[i + 1, j], a[j] - F[i, j - 1]}。
代码:
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 5010;
int a[N];
int n;
int f[N][N];
int main(void) {
scanf("%d", &n);
int sum = 0;
for (int i = 0; i < n; ++i) {
scanf("%d", a + i);
sum += a[i];
f[i][i] = a[i];
}
for (int i = n - 1; i >= 0; --i) {
for (int j = i + 1; j < n; ++j) {
f[i][j] = max(a[i] - f[i + 1][j], a[j] - f[i][j - 1]);
}
}
int diff = f[0][n - 1] > 0 ? f[0][n - 1] : -f[0][n - 1];
printf("%d\n", (sum + diff) >> 1);
return 0;
}
滚动数组优化:
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 5010;
int a[N];
int n;
int f[N];
int main(void) {
scanf("%d", &n);
int sum = 0;
for (int i = 0; i < n; ++i) {
scanf("%d", a + i);
sum += a[i];
f[i] = a[i];
}
for (int i = n - 1; i >= 0; --i) {
for (int j = i + 1; j < n; ++j) {
f[j] = max(a[i] - f[j], a[j] - f[j - 1]);
}
}
int diff = f[n - 1] > 0 ? f[n - 1] : -f[n - 1];
printf("%d\n", (sum + diff) >> 1);
return 0;
}