P4597 序列 sequence
是CF13C Sequence的加強版,\(N\leq 5*10^5\)。
如果想了解\(O(N^2)\)的DP解法請看此文。
給定\(N\)個數,每次操作可以選其中一個數\(+1\)或\(-1\)。請問要讓這個數列不降,最少需要多少次操作?
看到資料範圍發現不能用\(O(N^2)\)的dp了,需要換一種思路。
我們用類似貪心的方法做,從左往右找\(i<j,a_i>a_j\)(注意\(i,j\)不一定連續,後面就懂了),然後對它們的值進行上調下調。
如上圖,我們發現\(10\to 1\)不滿足“不降”,需要進行調整。我們想到在\([1,10]\)中找到一箇中間點,把這\(2\)個數都移動到中間點。而無論中間點是幾,這一操作代價都為\(10-1=9\)。那麼我們為了讓操作次數儘可能小,我們應該儘可能讓中間點往下,即移動到\(1\)。
總結:如果遇到\(x\)在\(y\)後面,而\(x>y\),我們需要把\(y\)下移到\(x\)的位置。從左到右遍歷每條邊,重複上述操作即可。
大家可能會有疑惑:如果\(y\)前面的\(z\),滿足\(x<z<y\),如果把\(y\)下移到\(x\),就會破壞前面的“不降”:
-
如果\(z\)後面又遇到一個值\(a_i<a_{i-1}\),而它之前的最小值正是\(z\),它就會把\(z\)壓下去。
但如下圖,我們又發現,\(z\)下調後,後邊又不滿足不降,此時就把\(x\)作為\(z\)迴圈上面的步驟。
-
如果\(z\)後面沒有更低的值,則說明\(y\)沒必要降到\(x\),中間點可以上移到\(z\)也不會影響結果,但在此同時我們讓原序列合法了。而中間點只要在\([x,y]\)中間,消耗就相同,所以答案不受影響。
綜上,答案不會受下移的影響,儘管序列可能不滿足“不降”,但我們可以在消耗不變的情況下調整至滿足條件。
接下來就是程式碼實現。為了維護前面的最大值,我們開一個優先佇列。
對於輸入的每一個\(x\):
首先加入\(x\)。
如果遇到\(x<max\),則\(ans+=max-x\),然後\(max\)出佇列,加入下調後的高度\(x\)。
時間複雜度\(O(N\log N)\)。
雖然加強版空間限制從64MB提升到了125MB,但是這份程式碼可以過CF的原題,因為空間消耗只有\(\log N\)而已。
注意開long long
。
點選檢視程式碼
#include<bits/stdc++.h>
#define int long long
using namespace std;
priority_queue<int> q;
int n,x,ans;
signed main(){
cin>>n;
while(n--){
cin>>x;
q.push(x);
if(x<q.top()){
ans+=q.top()-x;
q.pop();
q.push(x);
}
}
cout<<ans;
return 0;
}