ZOJ#1610-线段树区间更新

本文详细介绍了一道利用线段树解决的有趣题目,通过实战练习掌握线段树的区间覆盖操作,以及如何统计各种颜色形成的连续区间数量。适合初学者理解和实践线段树的基本应用。

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

acm复习第一站,线段树。处女题解就交给本校oj的题目吧。
题目挺有趣的,代码量适中,难度偏易,是一道练习线段树基础操作的好题。
ps.一定要注意,覆盖的不是点,是区间!题目给的n不是区间大小,是操作数!

题目链接
http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=1610
题目大意
  在一条长度为8000的线段上染色,每次将一段区间涂成某种颜色,后面染的色可以覆盖原来染的色。询问所有染色操作之后,各种颜色在线段表面各自形成了多少连续的区间。
数据范围
  端点,颜色,操作数,均不超过8000;
解题思路
  涂色操作是中规中矩的线段树区间覆盖的操作,对于最后的统计,一个比较容易想到的办法是从头开始一段段地找出同色的连续区间,然后在这段区间对应的颜色的计数表中+1。显然,可以直接遍历一次线段树出结果。但我当时给想复杂了,多维护了两个值,搞了一个函数,询问时能给出一个点右侧最长的与该点同色的区间终点。有兴趣的看看我的代码注释呀。
参考代码


#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int MAXN=8000;
int n;
struct node 
{
	int cover,start,well;
	/*
	cover是lazy标记,-1表示无标记。
	start表示区间起点颜色,-1表示无颜色。
	well==1表示区间同色,well==0表示区间不同色
	*/
	node operator + (const node &A)//结点合并
	{
		node P;
		if (cover==A.cover) P.cover=cover;
		else P.cover=-1;
		if (well&&A.well&&start==A.start) P.well=1;
		else P.well=0;
		P.start=start;
		return P;
	}
};
struct pr
{
	int end,color;//专用于查询函数,end表示同色区间的右端点,color表示该区间的颜色
};
struct Tree
{
	node cache[MAXN*4+10];
	void build()
	{
		build(1,1,MAXN);
	}
	void build(int root,int L,int R)
	{
		cache[root]=(node){-1,-1,1};
		if (R-L) build(root<<1,L,L+R>>1),build(root<<1|1,(L+R>>1)+1,R);
	}
	void setlazy(int root,int c)
	{
		cache[root]=(node){c,c,1};
	}
	void lazydown(int root)
	{
		if (~cache[root].cover)
		{
			setlazy(root<<1,cache[root].cover);
			setlazy(root<<1|1,cache[root].cover);
			cache[root].cover=-1;
		}
	}
	void paint(int l,int r,int c)
	{
		paint(1,1,MAXN,l,r,c);
	}
	void paint(int root,int L,int R,int l,int r,int c)
	{
		if (L>=l&&R<=r) {setlazy(root,c);return;}
		lazydown(root);
		int M=L+R>>1;
		if (l<=M&&r>=L) paint(root<<1,L,M,l,r,c);
		if (l<=R&&r>M) paint(root<<1|1,M+1,R,l,r,c);
		cache[root]=cache[root<<1]+cache[root<<1|1];
	}
	pr query(int st)
	{
		return query(1,1,MAXN,st);
	}
	pr query(int root,int L,int R,int st)
	{
		if (L==st&&cache[root].well) return (pr){R,cache[root].start};
		//当前结点是一个完整的同色区间,且左侧与待查点重合,直接返回区间右端点
		int M=L+R>>1;
		lazydown(root); 
		if (st>M) return query(root<<1|1,M+1,R,st);//若待查点在右儿子,直接递归右儿子
		pr A;
		A=query(root<<1,L,M,st);
		if (A.end==M&&cache[root<<1|1].start==A.color)
		//如果左侧同色区间到达左儿子的边界,且与右儿子的左端点同色,那么可以将该同色区间扩展到右儿子
			A=query(root<<1|1,M+1,R,M+1);
		return A;
	}
}T;
int cnt[MAXN+10];//各个颜色的计数表
int main()
{
	while (~scanf("%d",&n))
	{
		T.build();
		memset(cnt,0,sizeof(cnt));
		int l,r,c;
		for (int i=1;i<=n;i++)
		{
			scanf("%d%d%d",&l,&r,&c);
			T.paint(l+1,r,c);//将区间问题转化为点问题
		}
		int st=1;pr seg;
		while (st<=MAXN)
		{
			seg=T.query(st);
			if (~seg.color) cnt[seg.color]++;
			st=seg.end+1;//指针跳到该区间的末端
		}
		for (int i=0;i<=MAXN;i++) 
			if (cnt[i]) printf("%d %d\n",i,cnt[i]);
		printf("\n");
	}
	return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值