【ZJOI2020】序列【貪心】
阿新 • • 發佈:2021-06-15
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\) ,直到到達為 \(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\)
若 \(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; }