ZJOI2020 序列

ZJOI2020 序列 [* hard]

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

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

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

(nle 10^6,Tle 10,a_ile 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+Cle a_{i+1}),那么我们是可以直接交换 (B,C) 并往后处理的。

假设 (A+C>a_{i+1}),那么我们相当于需要确定一个最优的 (x,y) 使得 (x+y=a_{i+1},xle A,yle 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 ;
}
原文地址:https://www.cnblogs.com/Soulist/p/13732003.html