[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\) 还原成原来的样子,得到最终状态转移方程:

\[f_i = \max(f_ {i - 1}, a_i - f_{i - 1}) \]

\(\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!

posted @ 2022-04-23 11:02  dbxxx  阅读(23)  评论(0编辑  收藏  举报