bzoj1367

非常巧妙

 

先考虑只要求z1<=z2<=...<=znz1<=z2<=...<=zn:

两个特殊情况:

-t[1]<=t[2]<=...<=t[n]t[1]<=t[2]<=...<=t[n],此时z[i] = t[i].

-t[1]>=t[2]>=...>=t[n]t[1]>=t[2]>=...>=t[n],此时z[i]=x,x为序列t的中位数.

于是可以将原数列划分成m个区间,每一段的解为该区间的中位数。

实现:

假设已经求出了前k个数的最优解,被划分成了m个区间,每段区间的最优解为w[i](w[1]<=w[2]<=...<=w[m])w[i](w[1]<=w[2]<=...<=w[m]),现在考虑第k + 1个数,先将t[k + 1]单独看作一个区间,最优解为w[m+1],此时假如w[m]>w[m+1]w[m]>w[m+1],则合并区间m,m + 1,然后找出新区间的解(中位数),重复上述过程直到w[m]<=w[m+1]w[m]<=w[m+1].

如何维护中位数:当堆的大小大于区间长度的一半时删除堆顶元素,则堆中的元素一定是该区间内较小的一半元素,堆顶元素即为该区间的中位数。

这只是z1<=z2<=...<=znz1<=z2<=...<=zn的情况。。

然而要求递增只需要将原本的t[i]改成t[i] - i,再按照上述做法做就行了。

#include<cctype>
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=1e6+2;
int n,a[maxn],tot,root[maxn],w[maxn],l[maxn],r[maxn],dis[maxn],siz[maxn],tr[maxn][2];
inline void read(int &x){
    char ch=getchar();x=0;int f=1;
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
    x*=f;
}
inline int merge(int x,int y){
    if(!x || !y)return x+y;
    if(w[x]<w[y])swap(x,y);
    tr[x][1]=merge(tr[x][1],y);
    siz[x]=siz[tr[x][0]]+siz[tr[x][1]]+1;
    if(dis[tr[x][1]]>dis[tr[x][0]])swap(tr[x][0],tr[x][1]);
    dis[x]=dis[tr[x][1]]+1;
    return x;
}
inline void pop(int &x){x=merge(tr[x][0],tr[x][1]);}
inline int newnode(int x){
    w[++tot]=x;siz[tot]=1;tr[tot][0]=tr[tot][1]=dis[tot]=0;return tot;
}
int main(){
    read(n);
    for(int i=1;i<=n;i++)read(a[i]),a[i]-=i;
    int cnt=0;
    for(int i=1;i<=n;i++){
        cnt++;
        root[cnt]=newnode(a[i]);l[cnt]=r[cnt]=i;
        while(cnt>1 && w[root[cnt]]<w[root[cnt-1]]){
            root[--cnt]=merge(root[cnt],root[cnt+1]);r[cnt]=r[cnt+1];
            while(siz[root[cnt]]*2>r[cnt]-l[cnt]+2)pop(root[cnt]);
        }
    }
    long long ans=0;
    for(int i=1;i<=cnt;i++){
        int t=w[root[i]];
        for(int j=l[i];j<=r[i];j++)ans+=abs(t-a[j]);
    }
    printf("%lld\n",ans);
    return 0;
}

 

posted @ 2018-06-20 20:17  lnyzo  阅读(73)  评论(0编辑  收藏  举报