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;
}