BZOJ 1367: [Baltic2004]sequence

这里写图片描述

思考这道题目的时候毫无思路,而且数据范围很像是贪心题啊。其实我们可以考虑一些极端的情况,如何这个t序列就是递增的,那个直接zi=ti就好了,如果这个序列是递减的,我们可以让z序列全部等于t序列的中位数,这样显然是最优的。(证明略)但题目中z序列递增,所以做一个变换,让所有的zi-i,这样就保证递增了。接下来我们将递减序列进行合并,最后得到的就都是递增序列,直接赋值就好了,在将递减序列合并的时候,还要对应地合并这些序列的中位数,这项操作如何快速完成呢?想到利用可并堆(真不好想啊…),堆里存放的是这个序列前一半的元素,两个堆合并时保留合并的大序列的前一半元素,但这样好像并不一定得到中位数。比如将5 6 7 8 9和1 2 3 4合并,就得到1 2 5 6 7,但这样的情况并不会发生,因为我们每发现一个递减序列就合并, 很容易证得。

总结一下算法的过程,首先将输入序列进行变换,然后按顺序加入,如何发现存在两个序列的中位数递减,则可以进行合并,最后得到了递增的中位数序列,直接赋值相减。
Tips:absi2011的配对堆比我的左偏树高明得不知道到哪里去了。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
const int maxn=1000000+10;
int l[maxn],r[maxn],root[maxn],a[maxn],w[maxn],left1[maxn],right1[maxn],dis[maxn],v[maxn],n,tot;
int num[maxn],cnt[maxn];
int merge(int x,int y)
{
  if(!x||!y) return x+y;
  if(v[x]<v[y]) swap(x,y);
  r[x]=merge(r[x],y);
  if(dis[l[x]]<=dis[r[x]]) swap(l[x],r[x]);
  dis[x]=dis[r[x]]+1;
  return x;
}
int top(int x)
{
  return v[x];
}
void pop(int &x)
{
  x=merge(l[x],r[x]);
}
int main()
{
  //freopen("1367.in","r",stdin);
  //freopen("1367.out","w",stdout);
  scanf("%d",&n);
  for(int i=1;i<=n;i++) 
  {
    scanf("%d",&a[i]);
    a[i]-=i;
  }
  for(int i=1;i<=n;i++)
  {
    root[++tot]=i;v[i]=a[i];l[i]=r[i]=dis[i]=0;
    num[tot]=cnt[tot]=1;left1[tot]=i;right1[tot]=i;
    while(top(root[tot-1])>top(root[tot])&&tot>1)
    {
      tot--;root[tot]=merge(root[tot],root[tot+1]);
      num[tot]+=num[tot+1];cnt[tot]+=cnt[tot+1];right1[tot]=right1[tot+1];
      while(cnt[tot]>(num[tot]+1)/2)
      {
        pop(root[tot]);
        cnt[tot]--;
      }
    }
  }
  long long ans=0;
  for(int i=1;i<=tot;i++)
    for(int j=left1[i];j<=right1[i];j++)
      ans+=abs(top(root[i])-a[j]);
  printf("%lld\n",ans);
  return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值