[LOJ#3313]「ZJOI2020」序列

神贪 QAQ

题目链接

题目传送门

Solution

我们定义从 \(i\) 出发的一类操作为 \(i,i+1,i+2,\dots\) 加一,二类操作为 \(i,i+2,\dots\) 加一。

先说一个结论:

\(a_1>0,a_2>0\),则存在一种最优方案,至少有一次从 \(1\) 出发的一类操作。

证明:

考虑反证,分两种情况。
(1)从 \(1\) 出发了一个二类操作,\(2\) 出发了一个一类操作:
这时可以把 \(1\) 移出这个二类的操作范围,加入后面的一类操作,这样就形成了一个从 \(1\) 出发的一类操作和一个从 \(3\) 出发的二类操作,答案一定不会更劣。
(2)从 \(1\) 出发了一个二类操作,\(2\) 出发了一个二类操作:
设两次操作分别影响了 \(x\)\(y\) 个位置,则取 \(t=\min(x,y)\),把两次操作影响的前 \(t\) 个位置并成一个长度为 \(2t\) 的一类操作,剩下的 \(\max(x,y)-t\) 个位置必然会被一次二类操作覆盖,答案也不会更劣。

于是有一个贪心:设 \(t=\min(a_1,a_2)\),先从 \(1\) 出发 \(t\) 次一类操作(长度至少为 \(2\)),再从 \(1\) 出发 \(a_1-t\) 次二类操作。这样之后 \(a_1=0\),可以把 \(a_1\) 删掉之后继续考虑下一个数。

这样存在一个问题:上面的过程只影响了 \(a_1,a_2\) 两个数,对后面的数的影响无法快速处理。

故记录三个标记 \(A,B,C\) 表示可以当前免费从 \(i+1\) 出发 \(A\) 个一类操作和 \(B\) 个二类操作,从 \(i+2\) 出发 \(C\) 个二类操作。

然后 \(i\)\(1\)\(n\),如果从 \(a_i\) 出发了 \(t\) 次一类操作,则只需将 \(a_i\)\(a_{i+1}\) 都减掉 \(t\) 之后把 \(A\) 加上 \(t\),如果从 \(a_i\) 出发 \(s\) 次二类操作,则把 \(a_i\) 减掉 \(s\)\(C\) 加上 \(s\),之后让 \(i\) 加一并 swap 一下 \(B\)\(C\)

那么现在的主要问题就是处理之前的 \(A,B\) 标记对 \(i+1\) 的影响。如果 \(A+B\le a_{i+1}\) 则直接把 \(a_{i+1}\) 减掉 \(A+B\) 即可。

否则 \(a_{i+1}\) 减为 \(0\),但在这之前需要确定 \(x+y=a_{i+1},x\le A,y\le B\),从 \(i+1\) 出发 \(x\) 次一类操作和 \(y\) 次二类操作。\(x\)\(y\) 的具体值和后面的数有关。

\(K=A+B-a_{i+1}\),把 \(A\)\(B\) 分别减掉 \(K\),表示可以从 \(i+1\) 免费出发 \(K\)任意的操作。

这样有另一个问题:\(A\)\(B\) 会被减成负数。

考虑如果 \(A<K\),则在 \(B\) 次二类操作中一定至少有 \(K-A\) 次没有加在 \(i+1\) 上面。故可以把 \(B\) 减掉 \(K-A\),然后把 \(K\) 设为 \(A\)\(B<K\) 同理。这样之后就能保证 \(A\ge K,B\ge K\) 了。

这样处理之后就可以和之前一样,进行 \(\min(a_i,a_{i+1})\) 次一类操作和 \(\max(a_i-a_{i+1},0)\) 次二类操作了。

由于存在任意的(可以为一类或二类)从 \(i+1\) 出发的 \(K\) 次操作,故执行完之前的操作之后要把 \(a_{i+1}\) 重新加上 \(K\)。又由于这 \(K\) 次操作免费,故这时要让答案减掉 \(K\)

\(O(n)\)

Code

#include <bits/stdc++.h>

template <class T>
inline void read(T &res)
{
	res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	if (bo) res = ~res + 1;
}

typedef long long ll;

const int N = 1e5 + 5;

int n, a[N];

void work()
{
	ll ans = 0, A = 0, B = 0, C = 0; read(n);
	for (int i = 1; i <= n; i++) read(a[i]); a[n + 1] = 0;
	for (int i = 1; i <= n; i++, std::swap(B, C))
	{
		ll k = 0;
		if (A + B > a[i + 1])
		{
			k = A + B - a[i + 1];
			if (k > A) B -= k - A, k = A;
			if (k > B) A -= k - B, k = B;
			A -= k; B -= k;
		}
		a[i + 1] -= A + B + k; ll cnt1 = 0, cnt2 = 0;
		if (a[i] < a[i + 1]) cnt1 = a[i];
		else cnt1 = a[i + 1], cnt2 = a[i] - a[i + 1];
		a[i] -= cnt1; a[i + 1] -= cnt1; A += cnt1; a[i] -= cnt2; C += cnt2;
		ans += cnt1 + cnt2 - k; a[i + 1] += k;
	}
	printf("%lld\n", ans);
}

int main()
{
	int T; read(T);
	while (T--) work();
	return 0;
}
posted @ 2020-07-13 17:52  epic01  阅读(453)  评论(0编辑  收藏  举报