ZJOI2020 序列

ZJOI2020 序列 [* hard]

给定长度为 \(n\) 的序列,你可以:

  1. 选择一个区间 \([l,r]\),给区间所有数减 \(1\)
  2. 选择一个区间 \([l,r]\),给区间所有奇数下标的值减 \(1\)
  3. 选择一个区间 \([l,r]\),给区间所有偶数下标的值减 \(1\)

求最小操作次数,使得所有数变成 \(0\)

\(n\le 10^6,T\le 10,a_i\le 10^9\)

Solution

神仙贪心题,大概是欣赏的咕咕第一篇题解。

定义操作 \(1\) 为直线操作,即将 \(a_{l},a_{l+1}...a_r\) 集体减 \(1\)

定义操作 \(2\) 为跳线操作,即将 \(a_{l},a_{l+2}...\) 集体减 \(1\)

可以注意到一个强有力的性质:

  • 假设当前有 \(a_1,a_2>0\),那么至少有 \(1\) 次操作是从 \(a_1\) 处开始的直线。

考虑反证,如果没有,那么只有这样的情况:

  1. 操作是 \(a_1\) 跳线,\(a_2\) 直线。

显然等价于 \(a_1\) 直线,\(a_3\) 跳线(多的再直线)。

  1. 操作是 \(a_1\) 跳线,\(a_2\) 跳线。

显然不会优于,先 \(a_1\) 直线若干次,再 \(a_1/a_2\) 跳线。

这里的具体论证略为复杂,感性理解就好(

于是得到了一个贪心,对于 \(a_1\) 而言,操作 \(t=\min(a_1,a_2)\) 次直线,剩下的都直接跳线。

但是这样难以拓展到所有数的情况。

于是维护 \(A,B,C\) 表示可以免费\(a_i\) 出发的直线数为 \(A\),跳线数为 \(B\),从 \(i+1\) 出发的跳线数为 \(C\)

这样我们可以直接对 \(a_i\) 进行处理。

考虑如何拓展到 \(a_{i+1}\)

假设 \(A+C\le a_{i+1}\),那么我们是可以直接交换 \(B,C\) 并往后处理的。

假设 \(A+C>a_{i+1}\),那么我们相当于需要确定一个最优的 \(x,y\) 使得 \(x+y=a_{i+1},x\le A,y\le C\) 使得其可以方便的继续操作(因为多余的将无法衍生出去)。

\(K=A+C-a_{i+1}\),即我们多余的操作次数,那么我们会发现至少有 \(A-K\) 次直线和 \(C-K\) 条跳线是一定会执行的。

注意到 \(A+C-K=(A-K)+(C-K)+K=a_{i+1}\),所以我们还需要执行 \(K\) 次操作,这 \(K\) 次操作由之前的 \(A\)\(C\) 提供,同时是免费的,换而言之我们希望这 \(K\) 次操作由 \(i+1\)\(i+2\)(即往后的讨论)来帮我们解决此问题。

于是我们将答案减去 \(K\),将 \(A\)\(C\) 均减去 \(K\) 然后继续处理即可。

然而一个矛盾的点是,\(A\)\(C\) 不能为负数。

没关系,假设 \(A<K\),那么我们会发现有 \(C\) 类操作一定有 \(K-A\) 次没有用在 \(i+1\) 处(即假设多余操作均为 \(A\) 类,那么额外多余的操作一定还有 \(K-A\) 次)。

这样的话可以将 \(C\) 减去 \(K-A\),此时的 \(K'=A+C'-a_{i+1}=K-(K-A)=A\),所以将 \(K\) 设为 \(A\) 即可。

同理于 \(C\)

至此,本题在线性的复杂度完美解决。

\(Code:\)

#include<bits/stdc++.h>
using namespace std ;
#define Next( i, x ) for( register int i = head[x]; i; i = e[i].next )
#define rep( i, s, t ) for( register int i = (s); i <= (t); ++ i )
#define drep( i, s, t ) for( register int i = (t); i >= (s); -- i )
#define re register
#define int long long
int gi() {
	char cc = getchar() ; int cn = 0, flus = 1 ;
	while( cc < '0' || cc > '9' ) {  if( cc == '-' ) flus = - flus ; cc = getchar() ; }
	while( cc >= '0' && cc <= '9' )  cn = cn * 10 + cc - '0', cc = getchar() ;
	return cn * flus ;
}
const int N = 1e6 + 5 ; 
int T, n, a[N] ;  
signed main()
{
	T = gi() ; 
	while( T-- ) {
		n = gi() ; 
		rep( i, 1, n ) a[i] = gi() ; 
		int A = 0, B = 0, C = 0, ans = 0 ;
		a[n + 1] = 0 ; 
		for(re int i = 1; i <= n; ++ i) {
			a[i] -= min( A + B, a[i] ) ;
			int ta = min( a[i], max( 0ll, a[i + 1] - A - C ) ), tb = max( 0ll, a[i] - max( 0ll, a[i + 1] - A - C ) ) ;
			ans += (ta + tb), A += ta, B += tb ; 
			if( A + C > a[i + 1] ) {
				int K = A + C - a[i + 1] ;
				int ra = A - K, rc = C - K ;
				if( ra < 0 ) C -= (K - A), K = A ;
				if( rc < 0 ) A -= (K - C), K = C ;
				A -= K, C -= K, ans -= K ; 
			}
			swap( B, C ) ; 
		}
		cout << ans << endl ; 
	}
	return 0 ;
}
posted @ 2020-09-25 19:30  Soulist  阅读(263)  评论(0编辑  收藏  举报