【洛谷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;
}