【洛谷P4331】Sequence 数字序列

题目

题目链接:https://www.luogu.com.cn/problem/P4331
给定一个整数序列\(a_1, a_2, ··· , a_n\),求出一个递增序列\(b_1 < b_2 < ··· < b_n\),使得序列\(a_i\)\(b_i\)的各项之差的绝对值之和\(|a_1 - b_1| + |a_2 - b_2| + ··· + |a_n - b_n|\)最小。

思路

可以先将位置 \(i\) 上的数 \(a[i]\) 减去 \(i\),这样问题转化为求一个不降序列 \(b\)
答案必然是将整个序列划分为若干块,使得每一块均取其中位数,那么需要满足每一块的中位数单调不降。
可以用堆来维护每一块的中位数,假设前 \(i-1\) 个数字处理好了,加入第 \(i\) 个数字,那么就先把这个数字单独看成一块,然后不断判断如果最后一块的中位数小于前面一块的中位数,那么就将这两块的堆合并。然后需要不断弹出元素直到堆中元素数量不超过区间长度的一半。
合并堆十分简单,直接用左偏树做就可以了。而因为我们会合并当且仅当后面一块的中位数小于前面一块,所以合并之后前面一块的中位数显然会变小,所以我们直接维护大根堆即可。
最后输出方案的时候记得要加上 \(i\)
时间复杂度 \(O(n\log n)\)

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N=1000010;
int n,top,lc[N],rc[N],val[N],dis[N];
ll ans;

struct node
{
	int rt,l,r,size;
}st[N];

int read()
{
	int d=0; char ch=getchar();
	while (!isdigit(ch)) ch=getchar();
	while (isdigit(ch)) d=(d<<3)+(d<<1)+ch-48,ch=getchar();
	return d;
}
	
int merge(int x,int y)
{
	if (!x || !y) return x+y;
	if (val[x]<val[y]) swap(x,y);
	rc[x]=merge(rc[x],y);
	if (dis[lc[x]]<dis[rc[x]]) swap(lc[x],rc[x]);
	dis[x]=dis[rc[x]]+1;
	return x;
}

int main()
{
	n=read();
	dis[0]=-1;
	for (int i=1;i<=n;i++)
	{
		val[i]=read()-i;
		st[++top]=(node){i,i,i,1};
		while (top>1 && val[st[top].rt]<val[st[top-1].rt])
		{
			st[top-1].r=st[top].r; st[top-1].size+=st[top].size;
			st[top-1].rt=merge(st[top-1].rt,st[top].rt);
			top--;
			while (st[top].size>(st[top].r-st[top].l+1)/2+1)
			{
				st[top].rt=merge(lc[st[top].rt],rc[st[top].rt]);
				st[top].size--;
			}
		}
	}
	for (int i=1;i<=top;i++)
		for (int j=st[i].l;j<=st[i].r;j++)
			ans+=abs(val[st[i].rt]-val[j]);
	printf("%lld\n",ans);
	for (int i=1;i<=top;i++)
		for (int j=st[i].l;j<=st[i].r;j++)
			printf("%d ",val[st[i].rt]+j);
	return 0;
}
posted @ 2020-12-02 21:05  stoorz  阅读(143)  评论(0编辑  收藏  举报