P2184 贪婪大陆(线段树或树状数组+思维)

本文介绍了一种利用线段树解决区间染色问题的方法,关键在于理解染色区间不会覆盖已有颜色,而是增加新的颜色种类。通过建立两棵线段树分别存储起点和终点,可以高效地更新和查询区间内的颜色种类。代码示例包括线段树和树状数组两种实现方式。此外,还讨论了如果题目变为覆盖颜色时的解题思路。
摘要由CSDN通过智能技术生成

题目链接:贪婪大陆 - 洛谷

分析:先说一个容易引起误会的地方,就是同一个地方可以存在多种颜色,这也是本道题目最大的坑点,我一开始就以为后来染色会把之前染过色的地方覆盖掉,那这样就不太好处理了,而这道题目告诉我们对一段区间进行染色,颜色只能是这段区间不存在的,所以这也就给了我们一点启发意义:我们并不需要真正的对染的颜色关注太多,因为我们不可能去遍历哪一种颜色在该区间未出现过,下面给出正解:

我们只需要建立两棵线段树,一棵线段树中存储染色区间的起点,另一棵线段树中存储染色区间的终点,然后我们每次对一块区间[l,r]进行染色只需要把区间左端点的位置在第一棵线段树上加1,把区间右端点的位置在第二棵线段树上加1,这样我们就保证了区间[l,r]上一定有该号颜色(因为我们选取的颜色之前在区间[l,r]上是不存在的),那我们怎么知道一块区间[l,r]上有多少种颜色呢?我们可以这样考虑,可能出现在区间[l,r]上的染色区间肯定满足左边界在r前面,所以我们可以先找一下之前染色区间左边界在r之前的有多少种颜色,这些染色区间的右边界有两种情况,一种是在[1,l-1],一种是在[l,+无穷大],第一种情况的染色区间不会与[l,r]有交集,而后一种情况的染色区间一定会与[l,r]有交集,所以我们只要用染色区间左边界在r及之前的染色区间数量减去染色区间右边界在l之前的染色区间数量即可得到目标区间的颜色种类。

可以用线段树实现,也可以用树状数组实现,下面分别给出两种代码,后面还会对本题做出相应变形

线段树版本代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
using namespace std;
const int N=1e6+10;
int l[2][N],r[2][N],sum[2][N];
void pushup(int t,int id)
{
	sum[t][id]=sum[t][id<<1]+sum[t][id<<1|1];
}
void build(int t,int id,int L,int R)
{
	l[t][id]=L;r[t][id]=R;sum[t][id]=0;
	if(L==R) return ;
	int mid=L+R>>1;
	build(t,id<<1,L,mid);
	build(t,id<<1|1,mid+1,R);
}
void update_point(int t,int id,int x,int val)
{
	if(l[t][id]==r[t][id])
	{
		sum[t][id]+=val;
		return ;
	}
	int mid=l[t][id]+r[t][id]>>1;
	if(x<=mid) update_point(t,id<<1,x,val);
	else update_point(t,id<<1|1,x,val);
	pushup(t,id);
}
int query_interval(int t,int id,int L,int R)
{
	if(l[t][id]>=L&&r[t][id]<=R) return sum[t][id];
	int mid=l[t][id]+r[t][id]>>1;
	int ans=0;
	if(L<=mid) ans+=query_interval(t,id<<1,L,R);
	if(mid+1<=R) ans+=query_interval(t,id<<1|1,L,R);
	return ans;
}
int main()
{
	int n,m;
	cin>>n>>m;
	build(0,1,1,n);//储存起点 
	build(1,1,1,n);//储存终点 
	for(int i=1;i<=m;i++)
	{
		int op,a,b;
		scanf("%d%d%d",&op,&a,&b);
		if(op==1)
		{
			update_point(0,1,a,1);
			update_point(1,1,b,1);
		}
		else
		{
			int ans=query_interval(0,1,1,b)-query_interval(1,1,1,a-1);
			printf("%d\n",ans);
		}
	}
	return 0;
}

树状数组版本代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
using namespace std;
const int N=1e6+10;
int c[2][N],n,m;
int lowbit(int x)
{
	return x&(-x);
}
int sum(int k,int x)
{
	int ans=0;
	for(;x;x-=lowbit(x))
		ans+=c[k][x];
	return ans;
}
void add(int k,int x,int val)
{
	for(;x<=n;x+=lowbit(x))
		c[k][x]+=val;
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int op,a,b;
		scanf("%d%d%d",&op,&a,&b);
		if(op==1)
		{
			add(0,a,1);
			add(1,b,1);
		}
		else
		{
			int ans=sum(0,b)-sum(1,a-1);
			printf("%d\n",ans);
		}
	}
	return 0;
}

倘若这道题目是覆盖颜色,那就需要用另一种方法来解决了,就是说每次输入一段区间告诉你将要染色的颜色,然后问你某段区间内存在的颜色种数,下面给出颜色种数较少情况下的思路分析

我们可以把一个区间存在的颜色用一个数的二进制来表示,其实这个很简单,比如某一段区间的颜色是9,其对应的二进制为1001,这就意味着这段区间上存在2种颜色,也就是说某段区间的颜色种数就是这段区间颜色值的二进制表示中1的个数,至于pushup操作,就是当前区间的两个子区间的或值,然后剩下的就是一个线段树模板了

具体题目见:Count Color(线段树)_AC__dream的博客-CSDN博客

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值