BZOJ 1367 [Baltic2004]sequence 解题报告

这题在 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;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值