D. Trash Problem(线段树)

这篇博客介绍了如何使用离散化和线段树数据结构来解决一个涉及垃圾合并的问题。在n堆垃圾中,每堆垃圾的坐标不同,合并两堆垃圾的代价是它们坐标差的绝对值。通过离散化坐标并建立线段树,可以高效地处理添加和删除垃圾的操作,并实时计算每次操作后的最小合并费用。博客详细讲解了线段树的构建、更新和查询过程,展示了一个AC代码实现。
摘要由CSDN通过智能技术生成
题目传送门

Trash Problem

题目大意

有n堆垃圾,每一堆的坐标为x,合并两堆垃圾的花费为这两堆垃圾的坐标之差的绝对值。
你可以进行q次操作。1 x表示在x位置上增加一堆垃圾,0 x表示删除x位置上的垃圾。
输出将这n堆垃圾合并成两堆垃圾所需的花费,以及每次操作之后改变的答案。

思路

每次合并垃圾之后,所有相邻的垃圾的坐标差去掉最大值之后的和即为此次合并的最小花费
先将所有需要用的数进行一个离散化,再用这个离散化之后的数组建树。
用权值线段树维护:
相邻的两个有效数的差的最大值max。u段的max等于 两个子段中的max 和 右子段的左端点值(最大值)-左子段的右端点值(最小值)的最大值。因此我们还需要维护两个额外信息:这一段中的最大值ma和最小值mi。

AC Code

#include <iostream>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#include <map>
#include <queue>
#include <vector>
#include <set>
#include <algorithm>
#include <iomanip>
#define LL long long
#define PII pair<int,int>
using namespace std;
const int N=2e5+5,INF=0x3f3f3f3f;
struct Node{
	int mi,ma;	//每一段的最小值、最大值
	int max;	//这一段中相邻两个数的差的最大值
}tr[N*4];
int a[N],b[N];
bool st[N];		//标记每个点十分在当前序列中
int op[N],q[N];	//记录q次操作的询问(离线算法)
void pushup(int u)			//注意不要让st[i]=0的点去跟新信息
{
	tr[u].ma=max(tr[u<<1].ma,tr[u<<1|1].ma);
	
	if(tr[u<<1].mi==0||tr[u<<1|1].mi==0) tr[u].mi=max(tr[u<<1].mi,tr[u<<1|1].mi);
	else tr[u].mi=min(tr[u<<1].mi,tr[u<<1|1].mi);
	
	if(tr[u<<1].ma==0||tr[u<<1|1].mi==0) tr[u].max=max(tr[u<<1].max,tr[u<<1|1].max);
	else tr[u].max=max(max(tr[u<<1].max,tr[u<<1|1].max),tr[u<<1|1].mi-tr[u<<1].ma);
}
void build(int u,int l,int r)
{
	if(l==r)
	{
		if(st[l]) tr[u]={a[l],a[l],0};		//因为此段只有一个点,因此max=0
		else tr[u]={0,0,0};					//st[i]=0的点统一赋成 0 0 0
	}
	else
	{
		int mid=l+r>>1;
		build(u<<1,l,mid),build(u<<1|1,mid+1,r);
		pushup(u);
	}
}
void modify(int u,int l,int r,int x,bool op)
{
	if(l==r)
	{
		if(op) tr[u]={a[l],a[l],0};		//op=1,加入一个数
		else tr[u]={0,0,0};				//op=0,删除一个数
	}
	else
	{
		int mid=l+r>>1;
		if(x<=mid) modify(u<<1,l,mid,x,op);
		else modify(u<<1|1,mid+1,r,x,op);
		pushup(u); 
	}
}
int query()		//最小合并费用即为整段的相邻数的差(最大值-最小值)-相邻数差的最大值max
{
	return tr[1].ma-tr[1].mi-tr[1].max;
}
int main()
{
	int n,Q;
	scanf("%d %d",&n,&Q);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		b[i]=a[i];				//b[]备份初始的n个数
	}
	for(int i=1;i<=Q;i++)		//将所有需要用到的坐标放入a[]中
	{
		scanf("%d %d",&op[i],&q[i]);
		a[i+n]=q[i];
	}
	sort(b+1,b+1+n);
	sort(a+1,a+1+n+Q);					//离散化
	int cnt=unique(a+1,a+1+n+Q)-a-1;
	for(int i=1;i<=Q;i++)
		q[i]=lower_bound(a+1,a+1+cnt,q[i])-a;
	
	int k=1;
	for(int i=1;i<=n;i++)		//将初始的n个数进行标记
	{
		while(b[i]!=a[k]) k++;
		st[k]=true;
	}
	build(1,1,cnt);				//建树
	printf("%d\n",query());		//查询
	for(int i=1;i<=Q;i++)
	{
		modify(1,1,cnt,q[i],op[i]);
		printf("%d\n",query());
	}
	return 0;
}
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值