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; }