[BOI2004]Sequence 數字序列
阿新 • • 發佈:2019-02-08
特點 har 區間合並 一段 sin lin 一個 bits using (x為a中位數)
加入隊尾,令\(w_{cnt+1}=a_{k+1}\),如果\(w_{cnt}>w_{cnt+1}\),就將最後兩個區間合並,並找出新區間的最優解。重復上述過程,直至滿足\(w\)單調遞增。
PS:參考了黃源河的論文《左偏樹的特點及其應用》
題目描述:給定一個整數序列\(a_1, a_2, … , a_n\),求一個遞增序列\(b_1 < b_2 < … < b_n\),使得序列\(a_i\)和\(b_i\)的各項之差的絕對值之和 \(|a_1 - b_1| + |a_2 - b_2| + … + |a_n - b_n|\) 最小。
不難發現兩條性質:
①:若原序列a滿足\(a_1 < a_2 < … < a_n\),顯然最優情況為\(b_i=a_i\)
②:若原序列a滿足\(a_1 > a_2 > … > a_n\),顯然最優情況為\(b_{mid}=x\)
有了上述的兩種情況,不難發現,整個a序列是尤一些單調區間組成。
所以我們可以將原序列a拆成若幹個單調區間,最後再將答案合並。
那兩段區間的答案怎麽合並呢?
我們可以重新找一個中位數來合並即可。
不斷的找中位數,不難想到這道題,可是那道題是一個一個加入進堆,而現在我們要解決的是將兩個堆合並來找中位數,直接上二叉堆合並復雜度為\(O(n)\),所以不難想到可並堆(這裏使用左偏樹)。
假設我們已經找到前k個數的最優解,隊列中有\(cnt\)段區間,每段區間最優解為\(w_1,w_2,…,w_{cnt}\),現在要加入\(a_{k+1}\),並更新隊列。
首先把\(a_{k+1}\)
註意:題目要求的是一個遞增序列b,可以用減下標來實現(即輸入時把每個數都減去對應下標,輸出時加上),這樣就可以將遞增序列轉化成不下降序列,這樣就可以保證每一段區間的序列b不一樣了(原本有很多連續一段區間全是中位數,不能保證遞增)。
代碼如下:
#include<bits/stdc++.h> using namespace std; #define il inline #define re register #define debug printf("Now is Line : %d\n",__LINE__) #define file(a) freopen(#a".in","r",stdin);freopen(#a".out","w",stdout) #define ll long long #define mod 1000000007 il int read() { re int x=0,f=1;re char c=getchar(); while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();} while(c>='0'&&c<='9') x=x*10+c-48,c=getchar(); return x*f; } #define _ 1000006 int n,dis[_],ch[_][2],cnt; ll a[_],b[_],ans; struct node{int root,ls,rs,size,val;}e[_]; il int merge(int x,int y) { if(!x||!y) return x+y; if(a[x]<a[y]||(a[x]==a[y]&&x>y)) swap(x,y); ch[x][1]=merge(ch[x][1],y); if(dis[ch[x][0]]<dis[ch[x][1]]) swap(ch[x][0],ch[x][1]); dis[x]=dis[ch[x][1]]+1; return x; } int main() { n=read(); dis[0]=-1; for(re int i=1;i<=n;++i) a[i]=read()-i; for(re int i=1;i<=n;++i) { e[++cnt]=(node){i,i,i,1,a[i]}; while(cnt>1&&e[cnt].val<e[cnt-1].val) { --cnt; e[cnt].root=merge(e[cnt].root,e[cnt+1].root); e[cnt].size+=e[cnt+1].size; e[cnt].rs=e[cnt+1].rs; while(e[cnt].size*2>e[cnt].rs-e[cnt].ls+2) { --e[cnt].size; e[cnt].root=merge(ch[e[cnt].root][0],ch[e[cnt].root][1]); } e[cnt].val=a[e[cnt].root]; } } for(re int i=1;i<=cnt;++i) { for(re int j=e[i].ls;j<=e[i].rs;++j) { b[j]=e[i].val; ans+=abs(a[j]-b[j]); } } printf("%lld\n",ans); for(re int i=1;i<=n;++i) printf("%lld ",b[i]+i); return 0; }
[BOI2004]Sequence 數字序列