BZOJ 1367 [Baltic2004]sequence 解題報告
阿新 • • 發佈:2018-12-10
BZOJ 1367 [Baltic2004]sequence
Description
給定一個序列\(t_1,t_2,\dots,t_N\),求一個遞增序列\(z_1<z_2<\dots<z_N\),使得\(R=|t_1-z_1|+|t_2-z_2|+\dots+|t_N-z_N|\)的值最小,本題中,我們只需求出這個最小的\(R\)值
Input
第\(1\)行為\(N(1\le N\le10^6)\)
第\(2\)行到第\(N+1\)行,每行一個整數。第\(K+1\)行為\(t_k(0\le t_k \le 2\times 10^9)\)
思路:
如果我們需要求一個非遞減的\(z\)
考慮特殊情況
注意到對一個遞增的序列,有\(z_i=t_i\)
對一個遞減的序列,有\(z_i\)為\(t_i\)的中位數
所以猜測可以找到一種區間的劃分,使每一段區間的\(z_i\)都是這段區間\(t_i\)的中位數
感性理解一下感覺是對的。
實現起來需要動態維護末尾的區間的中位數,我們每次新進一個元素的時候,自己成一個區間,然後比一下和末尾的區間的中位數,比\(\text{Ta}\)小就合併了。注意到可以用堆維護中位數,然後合併就可並堆了。
最後一點,上面的情況都是針對非遞減的情況,改成遞增也很簡單,把\(t_i=t_i-i\)就行了,感性理解一下似乎挺容易的。
Code:
#include <cstdio> #include <cmath> #include <algorithm> #define ll long long const int N=1e6+10; int n,m,dis[N],key[N],rig[N],root[N],siz[N],ch[N][2]; #define ls ch[x][0] #define rs ch[x][1] int Merge(int x,int y) { if(!x||!y) return x+y; if(key[x]<key[y]) std::swap(x,y); rs=Merge(rs,y); if(dis[ls]<dis[rs]) std::swap(ls,rs); dis[x]=dis[rs]+1; siz[x]=siz[ls]+siz[rs]+1; return x; } void maintain() { int x; while(rig[m]-rig[m-1]+1>>1<siz[x=root[m]]) root[m]=Merge(ls,rs); } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%d",key+i),key[i]-=i; ++m,rig[m]=root[m]=i,siz[i]=1; while(key[root[m-1]]>key[root[m]]) root[m-1]=Merge(root[m-1],root[m]),rig[m-1]=rig[m],--m,maintain(); } ll ans=0; for(int i=1;i<=m;i++) for(int j=rig[i-1]+1;j<=rig[i];j++) ans=ans+abs(key[j]-key[root[i]]); printf("%lld\n",ans); return 0; }
2018.12.10