【贪心+可并堆】 [BalticOI 2004]Sequence 数字序列

题目

在这里插入图片描述
在这里插入图片描述

思路

首先考虑原题弱化版:相邻的b可以相等
于是我们可以得到两个性感感性的结论:

  1. 如果a递增,则b=a最优
  2. 如果a递减,b=a的中位数最优

根据这两个结论,我们可以得到一个比较感性的做法:
将原序列分成单调不升的m段,则每一段我们都取中位数。
然而这个做法不完全正确,比如我们取完中位数后是这样的序列:
3 3 3 2 2
我们发现这仍然不单调不降,所以我们需要合并这两个区间并重新取个中位数。
也就是说我们需要合并区间以求中位数。这个用左偏树可以做到log
那么原题怎么办?
我们可以对每一对ai,bi减一个i,这样答案不受影响

代码

#include<bits/stdc++.h>
#define ll long long
#define mod 1000000007
using namespace std; 
int read()
{
	int x=0,f=1; char c=getchar(); 
	while(c<'0'||c>'9') {if(c=='-') f=-1; c=getchar(); }
	while(c>='0'&&c<='9') x=x*10+c-48,c=getchar(); 
	return x*f; 
}
#define _ 1000006
int n,dis[_],ch[_][2],cnt; 
ll a[_],b[_],ans; 
struct node{int root,ls,rs,size,val; }e[_]; 
int merge(int x,int y)
{
	if(!x||!y) return x+y; 
	if(a[x]<a[y]||(a[x]==a[y]&&x>y)) swap(x,y); 
	ch[x][1]=merge(ch[x][1],y); 
	if(dis[ch[x][0]]<dis[ch[x][1]]) swap(ch[x][0],ch[x][1]); 
	dis[x]=dis[ch[x][1]]+1; 
	return x;   
}
int main()
{
	n=read();  dis[0]=-1; 
	for(int i=1; i<=n; ++i) a[i]=read()-i; 
	for(int i=1; i<=n; ++i)
	{
		e[++cnt]=(node){i,i,i,1,a[i]}; 
		while(cnt>1&&e[cnt].val<e[cnt-1].val)
		{
			--cnt; 
			e[cnt].root=merge(e[cnt].root,e[cnt+1].root); 
			e[cnt].size+=e[cnt+1].size; 
			e[cnt].rs=e[cnt+1].rs; 
			while(e[cnt].size*2>e[cnt].rs-e[cnt].ls+2)
			{
				--e[cnt].size; 
				e[cnt].root=merge(ch[e[cnt].root][0],ch[e[cnt].root][1]); 
			}
			e[cnt].val=a[e[cnt].root]; 
		}
	}
	for(int i=1; i<=cnt; ++i)
	{
		for(int j=e[i].ls; j<=e[i].rs; ++j)
		{
			b[j]=e[i].val; 
			ans+=abs(a[j]-b[j]); 
		}
	}
	printf("%lld\n",ans); 
	for(int i=1; i<=n; ++i) printf("%lld ",b[i]+i); 
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值