题目:洛谷P3210

题目描述:

有\(n\)堆石子,每堆石子的个数为\(a_i\),保证存在至少一堆石子个数为\(0\)

两个人,每个人每次可以取一堆石子,一堆石子可以被取当且仅当它相邻的石子有至少一堆为\(0\),第\(1\)堆石子的相邻石子堆只有第\(2\)堆,第\(n\)堆石子的相邻石子堆只有第\(n-1\)堆,第\(i\)\((1 < i < n)\)堆石子的相邻石子堆为第\(i-1\)堆和第\(i+1\)堆

这两个人都采取最优策略去使自己尽可能多地获得石子,求最后这两个人获得的石子个数分别是多少

\(2 \leq n \leq 10^6\),\(0 \leq a_i \leq 10^8\)

蒟蒻题解:

总的石子个数一定,两人都采取最优策略,对于每时每刻当前的先手来说,就是要使后来的所有操作先手的石子个数减去后手的石子个数尽量大

当存在连续\(3\)个非\(0\)的数\(a_{i-1}\),\(a_i\),\(a_{i+1}\),满足\(a_i \geq a_{i-1}\)且\(a_i \geq a_{i+1}\),要选\(a_i\)的话\(a_{i-1}\)和\(a_{i+1}\)中至少一个要被选

假设先手\(A\)选了\(a_{i-1}\),此时先手\(A\)的最优策略是选\(a_{i-1}\),说明选其他的策略对于先手\(A\)来说是更不优的,后手\(B\)转换为先手时,他肯定不可能去采取那些更不优的策略,且\(a_i \geq a_{i+1}\),在当前局面(不考虑已经被取了的石子)的情况下,他取\(a_i\),把\(a_{i+1}\)让给现在的后手\(A\)一定是最优的策略

然后后手\(A\)再转换为先手时,他会采取他当前的最优策略,由于对他来说,除了\(a_{i+1}\)之外,其他所有的策略都是没有之前取\(a_{i-1}\)来得优的,他采取其他的策略后,后手\(B\)一定会采取与那个策略相对应的策略,能获得比取\(a_i\)更好的收益效果,而不会取\(a_{i+1}\),所以\(a_{i+1}\)最后还是会让先手\(A\)来取

换句话说,假设\(a_{i+1}\)被后手\(B\)取了,说明对于后手\(B\)来说,取\(a_{i+1}\)是他的最优策略,那么\(B\)的上一步,\(A\)为先手时,\(A\)没有取\(a_{i+1}\),说明\(A\)有一个比取\(a_{i+1}\)更优秀的策略,那\(A\)完全可以把这个策略提到取\(a_{i-1}\)之前,让\(B\)去取\(a_{i-1}\),自己再取\(a_i\),再让\(B\)和现在的局势一样,这样的收益对于先手\(A\)来说一定更优

所以当\(a_i \geq a_{i-1}\)且\(a_i \geq a_{i+1}\),且先手取\(a_{i-1}\)或\(a_{i+1}\)时,先手一定会\(a_{i-1}\)和\(a_{i+1}\)两个都取,后手一定会取\(a_i\),可以把这三个石子堆合并起来,石子最多是相对对方尽量多,所以把这三个石子堆合并成\(a_{i-1}+a_{i+1}-a_i\)即可

通过上述的操作,可以把每一段用\(0\)隔开的子区间变成单调不降、单调不增和\(V\)字型

对于不在两边的区间,即中间的区间,不管是单调不降、单调不增还是\(V\)字型,要最优,肯定是取当前最大的,不然把当前最大的让给对方一定更不优,而这三种形状中当前最大的都是可以取得到的

对于在两边的区间,对于左边单调不降的部分和右边单调不增的部分,也都如中间的部分一起,也都是可以取得到最大的

至于左边单调不增的部分和右边单调不减的部分,如果是偶数个的话,假设先手\(A\)取了左边区间最靠右的石子堆,这是他当前的最优策略,后手\(B\)则可以取左边区间第二靠右的石子堆

如果\(B\)不是取左边区间第二靠右的石子堆,而是取别的,这样对\(B\)更优的话,那么\(A\)在之前操作的时候就可以取那个,让\(B\)来取左边区间最靠右的石子堆,这对之前的先手\(A\)来说肯定更优,所以\(B\)一定会接着取左边区间第二靠右的石子堆

这样就可以提前算出来左边区间单调不增部分的答案

如果左边区间单调不增的长度是奇数,可以把中间的转折点分到单调不减那,使得左边区间单调不增的长度是偶数,这样就可以通过上述的方法计算

右边区间单调不减的部分同左边区间单调不增的部分

由于需要排序,我使用的是快排,时间复杂度为\(\mathcal O(n log\ n)\),当然,你也可以使用归并排序,这样只需要\(\mathcal O(n)\)便能通过此题

参考程序:

#include<bits/stdc++.h>
using namespace std;
#define Re register int
typedef long long ll;

const int N = 1000005;
int n, siz, r, t, a[N], d[N];
ll ans1, ans2, q[N], g[N];

inline int read()
{
	char c = getchar();
	int ans = 0;
	while (c < 48 || c > 57) c = getchar();
	while (c >= 48 && c <= 57) ans = (ans << 3) + (ans << 1) + (c ^ 48), c = getchar();
	return ans;
}

inline void sol_pre(int x)
{
	if (!x) return;
	q[r = 1] = a[1];
	for (Re i = 2; i <= x; ++i)
	{
		q[++r] = a[i];
		while (r > 2 && q[r - 1] >= q[r - 2] && q[r - 1] >= q[r]) ans1 += q[r - 1], ans2 += q[r - 1], q[r - 2] += q[r] - q[r - 1], r -= 2;
	}
	while (r && q[r] >= q[r - 1]) g[++t] = q[r--];
	if (r & 1) g[++t] = q[r--];
	for (Re i = 1; i <= r; ++i)
		if (i & 1) ans2 += q[i];
		else ans1 += q[i];
}

inline void sol_nxt(int x)
{
	if (x > n) return;
	q[r = 1] = a[n];
	for (Re i = n - 1; i >= x; --i)
	{
		q[++r] = a[i];
		while (r > 2 && q[r - 1] >= q[r - 2] && q[r - 1] >= q[r]) ans1 += q[r - 1], ans2 += q[r - 1], q[r - 2] += q[r] - q[r - 1], r -= 2;
	}
	while (r && q[r] >= q[r - 1]) g[++t] = q[r--];
	if (r & 1) g[++t] = q[r--];
	for (Re i = 1; i <= r; ++i)
		if (i & 1) ans2 += q[i];
		else ans1 += q[i];
}

inline void sol(int x, int y)
{
	if (x > y) return;
	q[r = 1] = a[x];
	for (Re i = x + 1; i <= y; ++i)
	{
		q[++r] = a[i];
		while (r > 2 && q[r - 1] >= q[r - 2] && q[r - 1] >= q[r]) ans1 += q[r - 1], ans2 += q[r - 1], q[r - 2] += q[r] - q[r - 1], r -= 2;
	}
	for (Re i = 1; i <= r; ++i) g[++t] = q[i];
}

int main()
{
	n = read();
	for (Re i = 1; i <= n; ++i)
	{
		a[i] = read();
		if (!a[i]) d[++siz] = i;
	}
	sol_pre(d[1] - 1), sol_nxt(d[siz] + 1);
	for (Re i = 2; i <= siz; ++i) sol(d[i - 1] + 1, d[i] - 1);
	sort(g + 1, g + t + 1);
	if (t & 1) swap(ans1, ans2);
	for (Re i = t; i; --i)
		if ((t - i) & 1) ans2 += g[i];
		else ans1 += g[i];
	printf("%lld %lld", ans1, ans2);
	return 0;
}