洛谷P2734 [USACO3.3] 游戏 A Game 题解
题目链接:P2734 [USACO3.3] 游戏 A Game
题意:
有如下一个双人游戏:$N (2 \leq N \leq 100)$ 个正整数的序列放在一个游戏平台上,游戏由玩家 $1$ 开始,两人轮流从序列的任意一端取一个数,取数后该数字被去掉并累加到本玩家的得分中,当数取尽时,游戏结束。以最终得分多者为胜。
编一个执行最优策略的程序,最优策略就是使玩家在与最好的对手对弈时,能得到的在当前情况下最大的可能的总分的策略。你的程序要始终为第二位玩家执行最优策略。
输入格式:
第一行: 正整数 $N$,表示序列中正整数的个数。
第二行至末尾: 用空格分隔的 $N$ 个正整数($1\le N \le 200$)。
输出格式:
只有一行,用空格分隔的两个整数: 依次为玩家一和玩家二最终的得分。
先讲 dp 解法,还是很巧妙的,需要对博弈论理解深刻。
如果我们设 $f_{i,j}$ 表示区间 $[i,j]$ 先手的最优方案,那么有
其中 $S_i = \sum_{j=1}^{i}a_j$ 为前缀和。
为什么需要减去 $f$ 呢?因为如果先手取了 $a_i$ ,那后手的最优方案就是 $f_{i+1,j}$ ,因为先后手转换了。
时间复杂度 $\mathcal{O}(n^2)$
代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
void up(int &x,int y) { x < y ? x = y : 0; }
void down(int &x,int y) { x > y ? x = y : 0; }
#define N ((int)(105))
int n, a[N], sum[N], dp[N][N];
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
// freopen("check.in","r",stdin);
// freopen("check.out","w",stdout);
cin >> n;
for(int i = 1; i <= n; i++) {
cin >> a[i]; sum[i] = a[i] + sum[i - 1]; dp[i][i] = a[i];
}
for(int i = n - 1; i; i--)
for(int j = i + 1; j <= n; j++) {
dp[i][j] = sum[j] - sum[i - 1] - min(dp[i + 1][j], dp[i][j - 1]);
}
cout << dp[1][n] << ' ' << sum[n] - dp[1][n] << '\n';
return 0;
}
然后来讲贪心的解法。
不知道大家有没有觉得这道题很熟悉,之前有道题也是两头取,但是中间的数是存在一定单调性的。
考虑最简单的情况,即 $n=3$ 时,若 $a_2 > a_1$ 且 $a_2 > a_3$
那么先手能比后手多取到 $a_1 + a_3 - a_2$ ,于是我们把它看作一个数
即对于所有 $a_i > a_{i-1}$ 且 $a_{i} > a_{i + 1}$ 的 $i$ ,我们把 $a_{i-1},~a_{i},~a_{i+1}$ 缩成一个数 $a_{i+1}+a_{i-1}-a_i$ 。
重复执行这个过程直到 $a_i$ 成为关于 $i$ 的单谷函数,这样我们就可以贪心地从两头取了。
这样的做法唯一奇怪的地方就是我们真的可以这么缩这些数吗?事实上是可以的。
证明:
对于某个满足条件的 $i$ ,我们将它缩数后,它可能成为单谷函数的某一侧甚至谷部
当然它也可能成为一个新的峰的某一部分,而最终我们会将每个峰都拍成谷的一部分。$\square$
时间复杂度 $\mathcal{O}(n)$
代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
void up(int &x,int y) { x < y ? x = y : 0; }
void down(int &x,int y) { x > y ? x = y : 0; }
#define N ((int)(1e6 + 15))
int sum,top,stk[N];
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
// freopen("check.in","r",stdin);
// freopen("check.out","w",stdout);
int n; cin >> n;
for(int i = 1, x; i <= n; i++)
{
cin >> x; sum += x; stk[++top] = x;
while(top >= 3 && stk[top - 1] >= stk[top] && stk[top - 1] >= stk[top - 2]) {
stk[top - 2] = stk[top] + stk[top - 2] - stk[top - 1], top -= 2;
}
}
int l = 1, r = top, res = 0;
while(l <= r)
{
if(stk[l] > stk[r]) res += stk[l++]; else res += stk[r--];
if(l > r) break;
if(stk[l] > stk[r]) res -= stk[l++]; else res -= stk[r--];
}
cout << (sum + res) / 2 << ' ' << (sum - res) / 2 << '\n';
return 0;
}
参考文献:
[1] https://www.luogu.com.cn/article/v58woiz8
[2] https://www.luogu.com.cn/article/bp03gipm
洛谷博客都换新样了,我的生活什么时候换新样呢。