给定一个整数序列(a_1,a_2...a_n(nleq10^6)),求出一个递增序列(b_1<b_2<···<b_n),使得序列(a_i)和(b_i)的各项之差的绝对值之和(|a_1 - b_1| + |a_2 - b_2| + ··· + |a_n - b_n|)最小
这题挺有意思的
首先我们可以把(a_i)加上(i),那么我们求的(b_i)也加上(i),这样不但对结果没有影响,而且可以把b的单调递增限制转化为单调不降
然后我们考虑对a的一个单调不降的区间,那么答案肯定是(b_i=a_i)
而对于a的单调下降的区间,答案为(b_i=a_{mid}),(a_mid)为a的这段区间的中位数
很容易想到a肯定是由许多这样的两种区间构成的,那么对于两段a的单调下降区间,我们可以对其合并再求中位数
于是做法就出来了,我们开栈记录当前的最值(w_{cnt}),然后向后扫a,如果(a_i<w_{cnt}),那么就合并这两个点的中位数
中位数还是比较好求的,因为是中间大,所以用堆存个(size),如果比区间的一半还大就pop掉,这样最大值就是中位数了
这题还需要合并,所以开可并堆就好了
Code
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
const int N = 1e6;
using namespace std;
int n,lc[N + 5],rc[N + 5],rt[N + 5],size[N + 5],L[N + 5],R[N + 5],cnt,dist[N + 5];
long long ans,b[N + 5],a[N + 5],v[N + 5];
int merge(int x,int y)
{
if (!x || !y)
return x + y;
if (v[y] > v[x])
swap(x,y);
rc[x] = merge(rc[x],y);
if (dist[lc[x]] < dist[rc[x]])
swap(lc[x],rc[x]);
dist[x] = dist[rc[x]] + 1;
return x;
}
long long myabs(long long x)
{
return x > 0 ? x : -x;
}
int main()
{
scanf("%d",&n);
for (int i = 1;i <= n;i++)
scanf("%lld",&v[i]),v[i] -= i;
for (int i = 1;i <= n;i++)
{
a[++cnt] = v[i];
L[cnt] = R[cnt] = rt[cnt] = i;
size[cnt] = 1;
while (cnt > 1 && a[cnt] < a[cnt - 1])
{
rt[cnt - 1] = merge(rt[cnt],rt[cnt - 1]);
size[cnt - 1] += size[cnt];
R[cnt - 1] = R[cnt];
cnt--;
while (size[cnt] > (R[cnt] - L[cnt] + 1 + 2) / 2) //上取整
{
size[cnt]--;
rt[cnt] = merge(lc[rt[cnt]],rc[rt[cnt]]);
}
a[cnt] = v[rt[cnt]];
}
}
for (int i = 1;i <= cnt;i++)
for (int j = L[i];j <= R[i];j++)
b[j] = a[i];
for (int i = 1;i <= n;i++)
ans += myabs(b[i] - v[i]);
cout<<ans<<endl;
for (int i = 1;i <= n;i++)
printf("%lld ",b[i] + i);
return 0;
}