1. 程式人生 > 其它 >【ZJOI2020】序列【貪心】

【ZJOI2020】序列【貪心】

Description

有一個長度為 \(n\) 的非負整數序列 \(a_1, a_2, \dots, a_n\)。每一步你可以從以下三種操作中選擇一種執行:

  • 選擇一個區間 \([l, r]\),將下標在這個區間裡的所有數都減 1。
  • 選擇一個區間 \([l, r]\),將下標在這個區間裡且下標為奇數的所有數都減 1。
  • 選擇一個區間 \([l, r]\),將下標在這個區間裡且下標為偶數的所有數都減 1。

求最少需要多少步才能將序列中的所有數都變成 0。

\(n\le 10^5,a_i\le 10^9\)

Solution

首先題目的三種操作可以轉化為兩種操作:

  • 從某一點出發,儘可能向右延伸直到到達為 \(0\)
    的位置,將經過的點全部減 \(1\)
  • 從某一點出發,依次將右側與當前點奇偶性相同的點 \(-1\) ,直到到達為 \(0\) 的位置。

考慮從左到右,將所有數變為 \(0\),設當前正在考慮 \(a_i\),如果 \(a_{i+1}\)\(a_i\) 都不為 \(0\),那麼直接從 \(i\) 出發執行一操作一定比執行二操作優,於是我們一直執行一操作直到 \(a_i\)\(0\)\(a_{i+1}\)\(0\);然後若 \(a_i\not= 0\),再用二操作將 \(a_i\) 減到 \(0\)

由於前面的操作會對當前點造成影響,不妨設可能會影響到當前點的一操作有 \(x\)

種,二操作有 \(y\) 種。

\(a_i>x+y\) ,那麼直接減去 \(x+y\)。否則 \(x,y\) 中有部分操作在這裡就終止了,我們令 \(k=x+y-a_i\),然後先讓 \(x,y,a_i\) 都減去 \(k\),然後看作給 \(a_i\) 增加了 \(k\) 次免費操作的機會,讓它來決定是選一操作還是二操作,問題就迎刃而解了。但是 \(x\) 有可能小於 \(k\),那麼 \(k-x\) 個二操作一定延伸不了,直接讓 \(y-=k-x,k=x\) 即可。\(y<k\) 時同理。

最終複雜度 \(\mathcal O(n)\)

Code

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
typedef long long ll;
int n,T;
ll a[N],ans;
int main(){
	scanf("%d",&T);
	while(T--){
		scanf("%d",&n);
		for(int i=1;i<=n;++i) scanf("%lld",&a[i]);
		ll x=0,y=0,z=0;ans=0;
		for(int i=2;i<=n+1;++i){
			int d=0;
			if(a[i]<x+y){
				d=x+y-a[i];
				if(x<d) y-=d-x,d=x;
				if(y<d) x-=d-y,d=y;
				x-=d;y-=d;a[i]-=d;
			}
			a[i]-=x+y;
			ll tmp=min(a[i],a[i-1]);
			x+=tmp;
			a[i-1]-=tmp;a[i]-=tmp;ans+=tmp;
			tmp=a[i-1];
			z+=tmp;ans+=tmp;a[i-1]=0;
			ans-=d;a[i]+=d;
			swap(y,z);
		}
		printf("%lld\n",ans);
	}
	return 0;
}