【BZOJ】1112 [POI2008]砖块Klo 平衡树

本文介绍了一种利用滑动窗口结合平衡树解决特定问题的方法。通过将问题转化为求解滑动窗口内的中位数及总变动成本最小值,文章详细阐述了解题思路与实现过程,并附带完整的代码示例。

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

题目传送门

今天一晚上就做了一道题,好颓废啊……

这题其实还挺水的,只不过本蒟蒻一直都不太想的进去,所以一直WA,一直懵逼。

其实这题的题意是在n个数中连续取k个,把这k个数都改成一个相同的数m,使得Σabs(a[i]-m)最小。

很显然,当m为当前k个数的中位数时,当前k个数的改动最小。至于为什么,我也不知道……

然后我们就可以把这题转化为一个滑动窗口的题目,总共有n-k+1个窗口,求当前窗口的改动最小值。

因为是滑动窗口,需要加点、删点,还要取中位数和求和,我们不难想到平衡树。

除了求和那一段我卡了一会,其他的都挺水的。

注意:加点和删点时记得注意对节点所在子树权值和的修改。主要是我也不知道为什么我忘了这一点了,然后就一直WA……

orz hzwer,最后还是看他的代码改的程序,最后AC,不愧是一代神犇

附上AC代码:

#include <cstdio>
#include <cstdlib>
#include <cctype>
#include <algorithm>
#define N 100010
using namespace std;

struct tree{
	long long sum,w,g,rnd,size;
}t[N];
int son[N][2],rt;
long long n,m,a[N],ans,size,now,tmp,sum1,sum2,mid;

inline char nc(){
	static char ch[100010],*p1=ch,*p2=ch;
	return p1==p2&&(p2=(p1=ch)+fread(ch,1,100010,stdin),p1==p2)?EOF:*p1++;
}

inline void read(long long &a){
	static char c=nc();long long f=1;
	for (;!isdigit(c);c=nc()) if (c=='-') f=-1;
	for (a=0;isdigit(c);a=a*10+c-'0',c=nc());
	a*=f;return;
}

inline void updata(int k){
	t[k].size=t[son[k][0]].size+t[son[k][1]].size+t[k].g;
	t[k].sum=t[son[k][0]].sum+t[son[k][1]].sum+t[k].w*t[k].g;
	return;
}

inline void turn(int &k,long long x){
	int p=son[k][x];
	son[k][x]=son[p][x^1];
	son[p][x^1]=k;
	updata(k),updata(p),k=p;
	return;
}

inline void ist(int &k,long long x){
	if (k==0){
		k=++size;
		t[k].w=x,t[k].rnd=rand();
		t[k].g=t[k].size=1,t[k].sum=x;
		return;
	}
	++t[k].size,t[k].sum+=x;
	if (t[k].w==x) ++t[k].g;
	else if (t[k].w<x){
		ist(son[k][1],x);
		if (t[son[k][1]].rnd<t[k].rnd) turn(k,1);
	}
	else {
		ist(son[k][0],x);
		if (t[son[k][0]].rnd<t[k].rnd) turn(k,0);
	}
	return;
}

inline void del(int &k,long long x){
	if (!k) return;
	if (t[k].w==x){
		if (t[k].g>1){
			--t[k].g,--t[k].size,t[k].sum-=x;
			return;
		}
		if (son[k][0]*son[k][1]==0) k=son[k][0]+son[k][1];
			else if (t[son[k][0]].rnd<t[son[k][1]].rnd) turn(k,0),del(k,x);
				else turn(k,1),del(k,x);
	}
	else {
		--t[k].size;
		if (t[k].w<x) t[k].sum-=x,del(son[k][1],x);
			else t[k].sum-=x,del(son[k][0],x);
	}
	return;
}

inline void find(int k,long long x){
	if (!k) return;
	if (x>t[son[k][0]].size&&x<=t[son[k][0]].size+t[k].g){
		sum1+=t[son[k][0]].sum+(x-t[son[k][0]].size-1)*t[k].w;
		sum2+=t[son[k][1]].sum+(t[son[k][0]].size+t[k].g-x)*t[k].w;
		mid=t[k].w;
	}
	else if (x<=t[son[k][0]].size){
		sum2+=t[k].g*t[k].w+t[son[k][1]].sum;
		find(son[k][0],x);
	}
	else {
		sum1+=t[k].g*t[k].w+t[son[k][0]].sum;
		find(son[k][1],x-t[son[k][0]].size-t[k].g);
	}
	return;
}

inline void work(){
	sum1=sum2=0;long long k=(m+1)>>1;
	find(rt,k);
	ans=min(ans,mid*(k-1)-sum1+sum2-mid*(m-k));
	return;
}

int main(void){
	read(n),read(m);
	for (int i=1; i<=n; ++i) read(a[i]);
	int j=1;ans=0x7fffffffffffffff;
	for (int i=1; i<=m; ++i) ist(rt,a[i]);
	work();
	for (int i=m+1; i<=n; ++i) del(rt,a[i-m]),ist(rt,a[i]),work();
	printf("%lld",ans);
	return 0;
}


评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值