这题在 CF Round #371 C题 又出了一次,,数据还是这题数据的弱化版,那题n^2的DP可以过,BZOJ上好像还有一题一样的,数据也是弱化的好像,三倍经验。。。
题解:
左偏树
这题的话,是黄源河关于左偏树的那个东西的例题
首先假设求的序列不是严格递增,而是非递减,即 z1 <= z2 <= z3 <= ..... <= zn
考虑一个区间( l,r ),
如果区间里的序列 tl <= tl+1 <= tl+2 <= ..... <= tr ,那么这段区间的z[i] = t[i],对答案贡献0
如果区间里的序列 tl >= tl+1 >= tl+2 >= ..... >= tr ,那么这段区间的z[i] = 这段区间里t[i]中位数
然后将以上两种情况合并起来, 将原序列 t 分成若干个下降区间
假设现在在t[i],前i-1个 t 分成了m个下降的区间,
如果t[i] < 第m个区间的答案,将t[i]合并进第m个区间, 重新求区间的中位数
如果t[i] >= 第m个区间的答案,将t[i]单独成为第m+1个区间
每次合并区间后,都要不断尝试向前合并
最后将序列分成若干个下降区间且区间的答案非递减
这样做正确性请参考黄源河的那个东西(我是蒟蒻不会证)
求区间的中位数,可以用一个大根堆维护这个区间的值,当堆里东西的数量大于(n+1)/2时,pop堆顶,最后堆顶的值即为区间中位数
至于如何把严格上升转为非递减,,,,t[i] -= i 就好了嘛
code:
#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<vector>
#include<string>
#include<bitset>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
const int maxn = 1010000;
struct tree
{
int lc,rc,dist;
ll c;
}tr[maxn];
int merge( int x,int y )
{
if( !x || !y ) return x|y;
if( tr[x].c < tr[y].c ) swap( x,y );
int k = merge( tr[x].rc,y );
if( tr[k].dist >= tr[tr[x].lc].dist ) swap( tr[x].lc,k );
tr[x].rc = k;
tr[x].dist = tr[tr[x].lc].dist+1;
return x;
}
ll a[maxn];
int n,m;
int root[maxn],num[maxn],sum[maxn];
int main()
{
scanf("%d",&n);
for( int i=1;i<=n;i++ )
{
scanf("%lld",&a[i]);
a[i] = a[i]-i + n+1;
tr[i].c = a[i];
}
m = 1; sum[1] = 1; num[1] = 1; root[1] = 1;
for( int i=2;i<=n;i++ )
{
m++;
root[m] = i; sum[m] = 1; num[m] = 1;
while( tr[root[m]].c < tr[root[m-1]].c )
{
m--;
root[m] = merge( root[m],root[m+1] );
sum[m] += sum[m+1]; num[m] += num[m+1];
while( num[m] > (sum[m]&1)+(sum[m]>>1) )
{
num[m]--;
root[m] = merge( tr[root[m]].lc,tr[root[m]].rc );
}
}
}
int i=1;
ll ans = 0;
for( int j=1;j<=m;j++ )
{
int temp = i;
for( ;temp<i+sum[j];temp++ ) ans += abs( a[temp]-tr[root[j]].c );
i = temp;
}
printf("%lld\n",ans);
return 0;
}