【Luogu】 P4331 [BalticOI 2004] Sequence 数字序列

题目链接

点击打开链接

题目解法

首先做一个重要的转化:把 b i b_i bi 单调上升变为 b i b_i bi 单调不降
如何转化?将 a i − i a_i-i aii 变成新的 a i a_i ai,将 b i − i b_i-i bii 变新的 b i b_i bi,这样答案是不变的,且 b i b_i bi 变成了单调不降

考虑对于两段相邻序列 a 1 , . . . , a n a_1,...,a_n a1,...,an a n + 1 , . . . , a n + m a_{n+1},...,a_{n+m} an+1,...,an+m,最优的一组 b b b 分别为 b 1 , . . . , b n ( b 1 = b 2 = . . . = b n ) b_1,...,b_n(b_1=b_2=...=b_n) b1,...,bn(b1=b2=...=bn) b n + 1 , . . . , b n + m ( b b + 1 = b n + 2 = . . . = b n + m ) b_{n+1},...,b_{n+m}(b_{b+1}=b_{n+2}=...=b_{n+m}) bn+1,...,bn+m(bb+1=bn+2=...=bn+m)
考虑合并之后的 b 1 , . . . , b n + m b_1,...,b_{n+m} b1,...,bn+m
b 1 = u ,    b n + 1 = v b_1=u,\;b_{n+1}=v b1=u,bn+1=v

  1. u ≤ v u\le v uv b i b_i bi 的值不用改变,直接合并即可
  2. u > v u>v u>v
    令合并后的答案为 c 1 , . . . , c n + m c_1,...,c_{n+m} c1,...,cn+m
    考虑结论: c n ≤ u c_n\le u cnu
    证明:如果有 c n > u c_n>u cn>u,那么把 c 1 , . . . , c n c_1,...,c_n c1,...,cn 替换成 u u u 也是一组合法的解,且答案不会变劣
    同理可得 c n + 1 ≥ v c_{n+1}\ge v cn+1v
    考虑最优解 c 1 , . . . , c n + m ( c n ≤ u ,    c n + 1 ≥ v ) c_1,...,c_{n+m}(c_n\le u,\;c_{n+1}\ge v) c1,...,cn+m(cnu,cn+1v)
    c 1 , . . . , c n + m c_1,...,c_{n+m} c1,...,cn+m 替换为 a 1 , . . . , a n + m a_1,...,a_{n+m} a1,...,an+m 的中位数 k k k 答案不会更劣
    证明:若 c 1 > k c_1>k c1>k c n + m < k c_{n+m}<k cn+m<k,那么易知可以替换;若 c n < k c_n<k cn<k c n + 1 > k c_{n+1}>k cn+1>k,根据 1 − n 1-n 1n ≥ u \ge u u a i a_i ai 一定不小于一半, n + 1 − m n+1-m n+1m l e v le v lev a i a_i ai 一定不小于一半可以证得

所以可以一个一个添加数,一段一段往前合并即可

考虑如何合并?用左偏树维护前一半的最大值
考虑合并如何合并两个左偏树?直接合并即可
直接合并左偏树维护中位数在没有限制的两个序列中是不对的
但这道题中是有特殊限制的
在这里插入图片描述考虑新的中位数一定出现在橘色框内,且上面序列的橘色框是维护到了,但下面序列的橘色框内是没有维护到的,但因为只添加了一个数,且之前的中位数是大于上方序列的中位数的,所以下面序列的橘色框内没有数,所以直接合并恰好是可行的

时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N(1000100);
struct Node{
	int ed,rt,siz;
}stk[N];
int n,a[N],ans[N];
int lc[N],rc[N],dist[N],v[N];
inline int read(){
	int FF=0,RR=1;
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
	for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
	return FF*RR;
}
int merge(int x,int y){
	if(!x||!y) return x|y;
	if(v[x]<v[y]) swap(x,y);
	rc[x]=merge(rc[x],y);
	if(dist[rc[x]]>dist[lc[x]]) swap(lc[x],rc[x]);
	dist[x]=dist[rc[x]]+1; 
	return x;
}
int main(){
	n=read();
	for(int i=1;i<=n;i++) a[i]=read(),a[i]-=i;
	int top=0;
	for(int i=1;i<=n;i++){
		v[i]=a[i],dist[i]=1;
		Node t={i,i,1};
		while(top&&v[t.rt]<v[stk[top].rt]){
			t.rt=merge(t.rt,stk[top].rt);
			if((t.siz&1)&&(stk[top].siz&1)) t.rt=merge(lc[t.rt],rc[t.rt]);
			t.siz+=stk[top].siz;
			top--;
		} 
		stk[++top]=t;
	}
	for(int i=1,j=1;i<=n;i++){
		if(i>stk[j].ed) j++;
		ans[i]=v[stk[j].rt];
	}
	LL tot=0;
	for(int i=1;i<=n;i++) tot+=abs(ans[i]-a[i]);
	printf("%lld\n",tot);
	for(int i=1;i<=n;i++) printf("%d ",ans[i]+i);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值