bzoj 1367 [ Baltic 2004 ] sequence —— 左偏树
题目:https://www.lydsy.com/JudgeOnline/problem.php?id=1367
好题啊!论文上的题;
论文上只给出了不下降序列的求法:
先考虑特殊情况,如果原序列上升,那么答案序列相同即可,如果下降,那么答案序列取中位数;
那么对于跌宕起伏的原序列,可以先一个一个加入元素,每次加入一个作为一个新区间,中位数是自己;
因为答案序列要不下降,所以当前区间的中位数比前一个区间大的时候就要合并,归纳可知(感性理解)整个区间的答案是它们的中位数;
论文中有严谨证明:https://wenku.baidu.com/view/20e9ff18964bcf84b9d57ba1.html
取中位数可以用一个大小是全体一半的大根堆,又要合并,所以就用可并堆;
那么求上升序列呢?
有个很巧妙的技巧,就是把原序列 t[i] = t[i] - i,然后对于这个序列求不下降序列;
那么得到答案序列后,把答案序列的每个元素都 + i,就得到一个上升序列,而差值的和还是不变的。
代码如下:
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; typedef long long ll; int const maxn=1e6+5; int n,t[maxn],l[maxn],r[maxn],rt[maxn],siz[maxn],num[maxn]; int ls[maxn],rs[maxn],dis[maxn]; ll ans; int abb(int x){return x>0?x:-x;} int merge(int x,int y) { if(!x||!y)return x+y; if(t[x]<t[y])swap(x,y);//维护大根堆 rs[x]=merge(rs[x],y); siz[x]=siz[ls[x]]+siz[rs[x]]+1;//+1 if(dis[ls[x]]<dis[rs[x]])swap(ls[x],rs[x]); if(rs[x])dis[x]=dis[rs[x]]+1; else dis[x]=0; return x; } int main() { scanf("%d",&n); for(int i=1;i<=n;i++)scanf("%d",&t[i]),t[i]-=i; int nw=0; for(int i=1;i<=n;i++) { l[++nw]=r[nw]=i; rt[nw]=i; siz[rt[nw]]=num[nw]=1; while(nw>1&&t[rt[nw-1]]>t[rt[nw]]) { nw--; num[nw]+=num[nw+1]; r[nw]=r[nw+1]; rt[nw]=merge(rt[nw],rt[nw+1]); while(siz[rt[nw]]*2>num[nw]+1)//+1 rt[nw]=merge(ls[rt[nw]],rs[rt[nw]]); } } for(int i=1;i<=nw;i++) for(int j=l[i];j<=r[i];j++)ans+=abb(t[j]-t[rt[i]]); printf("%lld\n",ans); return 0; }