[luogu p3507] [POI2010]GRA-The Minima Game
\(\mathtt{Link}\)
P3507 [POI2010]GRA-The Minima Game - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
\(\mathtt{Description}\)
给定一个长度为 \(n\) 的正整数序列,A和B轮流取数,一次取任意多,一轮一方得分为取的数的最小值,双方策略均为最大化自己的得分减对手的得分,问最后A的得分减去B的得分。
\(\mathtt{Data} \text{ } \mathtt{Range} \text{ } \mathtt{\&} \text{ } \mathtt{Restrictions}\)
- \(1 \le n\le 10^6\)
- \(1 \le a_i \le 10^9\)
\(\mathtt{Solution}\)
考虑到题目中要求最小值,而排序不会影响答案,数据范围也支持我们排序。那就先排个序。
接下来说的 \(a\) 都是排过序的了。
先考虑双方的博弈策略:一定拿从大到小连续的一段数,也就是一定拿的是 \(a_i, a_{i +1}, \ldots, a_n\) 的一段数列。
为什么?
因为如果你连 \(a_i\) 都取了,取比 \(a_i\) 更大的数并不会影响到自己的得分(得分是选数最小值),但是不取会给对方留更多超越自己的机会。
考虑 dp。定义 \(f_i\) 为前 \(i\) 个数中,先手得分减去后手得分的最大值。一个很明显的方程:
\(f_i = \max(a_j - f_{j - 1})\),\(1 \le j \le i\)
什么意思呢?\(a_j - f_{j - 1}\) 其实描述的就是我从 \(i\) 取到 \(j\),我的分数减去你的分数。很好理解:A先从 \(i\) 取到 \(j\),我的得分是 \(a_j\)(因为 \(a_j\) 到 \(a_i\) 中最小的是 \(a_j\)),而B的最大得分自然是 \(f_{j - 1}\)(在 A 从 \(i\) 取到 \(j\) 后,剩下的从 \(1\) 到 \(j - 1\) 这一段相当于 B 为先手取数,得分 \(f_{j - 1}\))。
这个做法 \(n^2\),时间复杂度不是特别美丽。主要分数不美丽,不然暴力最美
方便叙述,把 \(a_j - f_{j - 1}\) 说成式子 \(F_j\).
观察到 \(f_i\) 也就是 \(j\) 从 \(1\) 到 \(i\) 的 \(F_j\) 最大值,而 \(j\) 从 \(1\) 到 \(i - 1\) 的 \(F_j\) 最大值已经被 \(f_{i - 1}\) 保存了。
\(f_i\) 相对于 \(f_{i - 1}\) 只是在 \(j\) 的取值范围拓展到了可以取到 \(i\) 了,那么我们只需要把 \(f_{i - 1}\) 和新的 \(F_i\) 比较一下最大值就可以了。也就是 \(f_i = \max(f_{i - 1}, F_i)\).
到这里还能理解吗?不能理解就多看看吧!相信你可以看懂。
最后把 \(F_j\) 还原成原来的样子,得到最终状态转移方程:
\(\mathtt{Time} \text{ } \mathtt{Complexity}\)
\(\operatorname{O}(n \log n)\)
因为排序。
状态转移是 \(n\) 的复杂度的。
\(\mathtt{Code}\)
/*
* @Author: crab-in-the-northeast
* @Date: 2022-04-06 23:23:22
* @Last Modified by: crab-in-the-northeast
* @Last Modified time: 2022-04-06 23:30:39
*/
#include <iostream>
#include <cstdio>
#include <algorithm>
inline int read() {
int x = 0;
bool flag = true;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-')
flag = false;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 1) + (x << 3) + ch - '0';
ch = getchar();
}
if (flag)
return x;
return ~(x - 1);
}
const int maxn = 1000005;
int a[maxn], f[maxn];
int main() {
int n = read();
for (int i = 1; i <= n; ++i)
a[i] = read();
std :: sort(a + 1, a + 1 + n);
f[1] = a[1];
for (int i = 2; i <= n; ++i)
f[i] = std :: max(f[i - 1], a[i] - f[i - 1]);
printf("%d\n", f[n]);
return 0;
}
不错的dp!