BZOJ 1112 [POI2008]砖块Klo Treap

本文介绍了一种使用平衡树解决砖柱高度调整问题的方法,目标是在限定操作次数下使连续K柱砖的高度一致。通过维护长度为K的区间中位数并计算其他数与该中位数之间的绝对差值之和,实现最小化操作次数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

N柱砖,希望有连续K柱的高度是一样的. 你可以选择以下两个动作 1:从某柱砖的顶端拿一块砖出来,丢掉不要了. 2:从仓库中拿出一块砖,放到另一柱.仓库无限大. 现在希望用最小次数的动作完成任务.

Input

第一行给出N,K. (1 ≤ k ≤ n ≤ 100000), 下面N行,每行代表这柱砖的高度.0 ≤ hi ≤ 1000000

Output

最小的动作次数

Sample Input

5 3
3
9
2
3
1

Sample Output

2

HINT

原题还要求输出结束状态时,每柱砖的高度.本题略去.






传送门

一个结论就是区间里面取中位数,总差值肯定最小。

画个数轴很容易证明出来的。

然后这题就变成了维护长度为k的区间的中位数,并且求出其它数和其差的绝对值之和。

可以用一个平衡树去搞,

维护结点的子树大小、子树sum;

然后求出所有比中位数小的,花费是中位数减去它们;

比中位数大的,花费是它们减去中位数。

每次区间变动,delete最前面的、insert最后面的即可。


明明打了重复结点看作一个点的做法……

竟然计算sum的时候总是漏点什么= =

话说treap真的比splay快好多= =



#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int 
	N=100005;
int n,K,tot,root;
int H[N];
ll ans;
struct Treap{
	int sz[N],occ[N],l[N],r[N],rnd[N];
	ll sum[N],val[N];
	void up(int &k){
		sz[k]=sz[l[k]]+sz[r[k]]+occ[k];
		sum[k]=sum[l[k]]+sum[r[k]]+val[k]*occ[k];
	}
	void rturn(int &k){
		int t=l[k];l[k]=r[t],r[t]=k;
		up(k),k=t,up(k);
	}
	void lturn(int &k){
		int t=r[k];r[k]=l[t],l[t]=k;
		up(k),k=t,up(k);
	}
	void insert(int &k,int x,int &tot){
		if (!k){
			k=++tot;
			sz[k]=occ[k]=1,l[k]=r[k]=0;
			sum[k]=val[k]=(ll)x,rnd[k]=rand();
			return;
		}
		sz[k]++,sum[k]+=(ll)x;
		if (x==val[k]){occ[k]++;return;}
		if (x<=val[k]){
			insert(l[k],x,tot);
			if (rnd[l[k]]<rnd[k]) rturn(k);
		} else{
			insert(r[k],x,tot);
			if (rnd[r[k]]<rnd[k]) lturn(k);
		}
	}
	void del(int &k,int x){
		if (!k) return;
		if (val[k]==x){
			if (occ[k]>1){sum[k]-=(ll)x,sz[k]--,occ[k]--;return;}
			if (!l[k] || !r[k]){k=l[k]^r[k];return;}
			if (rnd[l[k]]<rnd[r[k]]) rturn(k);
				else lturn(k);
			del(k,x);
			return;
		}
		sz[k]--,sum[k]-=(ll)x;
		if (x<=val[k]) del(l[k],x);
			else del(r[k],x);
	}
	ll SmallSum(int &k,int x){
		if (!k) return 0LL;
		if (val[k]<=x) return SmallSum(r[k],x)+sum[l[k]]+val[k]*occ[k];
			else return SmallSum(l[k],x);
	}
	ll BigSum(int &k,int x){
		if (!k) return 0LL;
		if (val[k]>x) return BigSum(l[k],x)+sum[r[k]]+val[k]*occ[k];
			else return BigSum(r[k],x);
	}
	int query_num(int &k,int x){
		if (!k) return 0;
		if (sz[l[k]]>=x) return query_num(l[k],x);
		if (sz[l[k]]+occ[k]<x) return query_num(r[k],x-sz[l[k]]-occ[k]);
		return val[k];
	}
	void query_pre(int &k,int x){
		if (!k) return;
		if (val[k]<=x){ans+=sz[l[k]]+occ[k],query_pre(r[k],x);}
			else query_pre(l[k],x);
	}
	void query_suc(int &k,int x){
		if (!k) return;
		if (val[k]>x){ans+=sz[r[k]]+occ[k],query_suc(l[k],x);}
			else query_suc(r[k],x);
	}
}tr;
int middle(){
	return tr.query_num(root,(K+1)>>1);
}
ll countans(int mid){
	ll tres;
	ans=0LL,tr.query_pre(root,mid);
	tres=ans*mid-tr.SmallSum(root,mid);
	ans=0LL,tr.query_suc(root,mid);
	tres+=tr.BigSum(root,mid)-ans*mid;
	return tres;
}
int main(){
	srand(283810);
	scanf("%d%d",&n,&K);
	for (int i=1;i<=n;i++) scanf("%d",&H[i]);
	tot=root=0;

	for (int i=1;i<=K;i++) tr.insert(root,H[i],tot);
	ll res=countans(middle());
	for (int i=K+1;i<=n;i++){
		tr.del(root,H[i-K]);
		tr.insert(root,H[i],tot);
		res=min(res,countans(middle()));
	}
	printf("%lld\n",res);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值