1. 程式人生 > 實用技巧 >【洛谷6631】[ZJOI2020] 序列(思維題)

【洛谷6631】[ZJOI2020] 序列(思維題)

點此看題面

大致題意: 給定一個序列,每次操作你可以選擇一段區間,然後將其中所有數/所有下標為奇數的數/所有下標為偶數的數都減\(1\),求最少操作多少次能夠讓全部數都變成\(0\)

前言

一道很妙的題目,看完題解感覺很簡單,但比賽的時候真的是隻會打暴力。。。

從第一個位置開始考慮

令將區間內所有數減\(1\)的操作為第一類操作,將區間內所有下標為奇數/偶數的數減\(1\)的操作為第二類操作。

對於第一個位置,處理所有以它為區間左端點的操作,顯然我們需要將它減成\(0\)

我們分三步貪心:儘可能進行第一類操作(進行\(\min\{a_1,a_2\}\)次),儘可能進行第二類操作(進行\(\min\{a_1,a_3\}\)

次),將第一個數減至\(0\)(進行\(a_1\)次)。

貪心的正確性在於,無論何時開始一種新的操作都要付出\(1\)的代價,而優先進行第一類操作肯定不會使答案變劣。

擴充套件到全域性

考慮把\(a_1\)變成\(0\)之後,緊接著就可以去按類似的步驟處理\(a_2\),然後是\(a_3,a_4,...,a_n\)

但問題在於當前處理到的位置可能先前已經進行過某些操作了。

\(cur,now\)分別表示當前位置進行過的第一類操作和第二類運算元。(這裡我們列舉的是\(a_2\)

首先,自然要將\(cur\)\(now\)分別向\(a_i\)\(min\)

然後,如果\(cur+now<a_i\)

,顯然可以直接將\(a_i\)減去\(cur+now\)

否則,我們令\(k=cur+now-a_i\),顯然\(cur-k\)次第一類操作和\(now-k\)次第二類操作都是必然要執行的,而剩餘的\(k\)次操作可以免費任選第一類操作或是第二類操作。

為了解決這樣的問題,我們可以將答案先減去\(k\),然後再操作完之後把\(a_i\)修改回\(k\),表示免費任選。

具體實現詳見程式碼。

程式碼

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
using namespace std;
int n,a[N+5];
class FastIO
{
	private:
		#define FS 100000
		#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
		#define D isdigit(c=tc())
		char c,*A,*B,FI[FS];
	public:
		I FastIO() {A=B=FI;}
		Tp I void read(Ty& x) {x=0;W(!D);W(x=(x<<3)+(x<<1)+(c&15),D);}
}F;
int main()
{
	RI Tt,i,t,k,cur,now,nxt;long long ans;F.read(Tt);W(Tt--)
	{
		for(F.read(n),i=1;i<=n;++i) F.read(a[i]);
		for(cur=now=nxt=ans=0,i=2;i<=n;++i,swap(now,nxt))//每次交換now和nxt
			cur>a[i]&&(cur=a[i]),now>a[i]&&(now=a[i]),//向a[i]取min
			cur+now>a[i]?(k=cur+now-a[i],cur-=k,now-=k,a[i]=0):(k=0,a[i]-=cur+now),//若cur+now>a[i]可以免費任選k次操作,否則直接減去
			ans+=(t=min(a[i-1],a[i])),a[i-1]-=t,a[i]-=t,cur+=t,ans+=a[i-1],nxt+=a[i-1],//優先進行第一類操作,然後是第二類操作
			k&&(ans-=k,a[i]=k);//免費任選k次操作的處理
		printf("%lld\n",ans+a[n]);//注意最後答案加上a[n]
	}return 0;
}