ural(Timus) 1019 Line Painting
线段树
题意:很明显的线段树。做了这题更加让我注意了用点和用段来建树的区别。这题是用点来表示线段的。一开始从0到10^9这个点之间的线段都是白色的,然后m个更新,每个更新 a,b,col,表示从点a到点b这条线段染成黑色或白色,问最后,白色线段中最长的是哪一段,输出它的位置(即线段两端的端点)
注意更新的时候a,b的数值范围是 0<a<b<10^9 , 其实这样给数据方便了处理,否则的话最后要搞一下挺烦的
先看看用段和用点表示线段树的区别。用点的话,0,1,表示的长度是1。1,3表示的长度是2。而用段的话1,3表示的长度是3,就是这个意思
线段树的建树,无论在何种情况下,应该说建树的原则都是二分长度,而和点或者段无关,但是由于点和段的区别要改变一下写法
如果是点
build(1,n,1); //其实总区间的长度是n-1,表示用第1个点到第n个点之间的线段来建树
所以二分的时候是这样的
mid=(l+r)>>1;
build(l,mid,lch(rt));
build(mid,r,rch(rt));
//注意这里的mid表示的依然是点,这样写才是二分长度
如果是段
build(1,n,1); //这里的长度实际上是n,表示第1段到第n段的单位线段来建树
所以二分的时候应该是这样
build(l,mid,lch(rt));
build(mid+1,r,rch(rt));
//这里mid表示的依然是第几段,这样做才是二分长度
说完了该说这题到底怎么解了,解法没什么特别,典型的解法
1.首先离散化,否则数据太大了,而用到的很少
2.整段更新区间的信息,为了提高效率用LAZY思想,即每次更新不要一直深入到叶子
(LAZY思想,cnt=1,表示该区间整段为黑色,cnt=0表示整段区间为白色,cnt=-1表示区间不止一种颜色,不确定)
3.最后一个查询,查询的意义是查询整个区间中一共有多少个,查询的时候,遇到cnt=1直接返回,因为该区间全部为黑,不必记录也不必继续深入了,遇到cnt=0,记录这个区间,并且返回,不必继续深入。如果是cnt=-1,就继续深入。
把白色的区间都保存在一个表中,表中没个元素表示一段白色的左右端点,而且我们可以知道这个表的端点是连续的
即 c[1].l < c[i].r <= c[2].l < c[2].r <= c[3].l < c[3].r <= ……………………c[n].l < c[n].r
为什么呢?因为查询函数,其实就是遍历函数,我们的遍历顺序决定了我们能得到一个有序的表
注意上面的不等式中有一些等号吧,是等号的地方就是说这两个区间其实是连着的,可以并为一个区间(为什么它们一开始的时候没有在一起是因为遍历的时候把它们拆开了)
所以我们扫描一次这个表,把连着的白色区间并起来,知道不能为止就计算它的长度,并不断更新最大长度,那我们就可以得到长度最长的区间了
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define MAX 1000000000 #define N 10010 #define M 5010 #define lch(i) ((i)<<1) #define rch(i) ((i)<<1|1) #define min(a,b) a<b?a:b #define max(a,b) a>b?a:b struct point{ int m,f,n; }p[N]; struct segment{ int l,r,v; }s[M]; struct tree{ int l,r,cnt; int mid() { return (l+r)>>1; } }t[4*N]; int m,num; struct ran{ int l,r; }c[N]; int nc; int cmp(struct point a ,struct point b) { return a.n<b.n; } void build(int l ,int r ,int rt) { t[rt].l=l; t[rt].r=r; t[rt].cnt=0; if(l+1==r) return ; int mid=t[rt].mid(); build(l,mid,lch(rt)); build(mid,r,rch(rt)); } void updata(int l ,int r ,int v ,int rt) { if(t[rt].cnt == v) return ; //剪枝 if(t[rt].l == l && t[rt].r == r) { t[rt].cnt=v; return ; } if(t[rt].cnt!=-1) //当前节点是纯色的,传递给它的孩子并且记录其为不纯色的 { t[lch(rt)].cnt=t[rch(rt)].cnt=t[rt].cnt; t[rt].cnt=-1; } int mid=t[rt].mid(); if(r<=mid) updata(l,r,v,lch(rt)); else if(l>=mid) updata(l,r,v,rch(rt)); else { updata(l,mid,v,lch(rt)); updata(mid,r,v,rch(rt)); } } void query(int rt) { if(t[rt].cnt==1) //该区间全部是黑色的没必要再继续下去 return ; if(t[rt].cnt==0) //该区间全部是白色的没必要继续下去,直接记录 { c[nc].l=t[rt].l-1; c[nc].r=t[rt].r-1; nc++; return ; } if(t[rt].l+1==t[rt].r) return ; //剩下的情况就是为-1的情况无法确定的 query(lch(rt)); query(rch(rt)); } void solve() { build(1,num,1); for(int i=0; i<m; i++) updata(s[i].l , s[i].r , s[i].v , 1); nc=0; query(1); //统计出所有白色的线段 // for(int i=0; i<nc; i++) printf("[%d,%d]\n",c[i].l,c[i].r); int maxlen=-1; int len; int resl,resr; int l,r; l=c[0].l; r=c[0].r; for(int i=0; i<nc-1; i++) { if(c[i+1].l == c[i].r) //可以继续合并一个区间 r=c[i+1].r; else //第i+1个区间不能合并进来 { len= p[r].n - p[l].n; if(len>maxlen) { maxlen=len; resl=l; resr=r; } l=c[i+1].l; r=c[i+1].r; } } len= p[r].n - p[l].n; if(len>maxlen) { resl=l; resr=r; } printf("%d %d\n",p[resl].n , p[resr].n); } int main() { while(scanf("%d",&m)!=EOF) { m++; s[0].v=0; p[0].f=0; p[0].m=0; p[0].n=0; p[1].f=1; p[1].m=0; p[1].n=MAX; int n=2; for(int i=1; i<m; i++) { int l,r; char col[5]; scanf("%d%d%s",&l,&r,col); s[i].v= (col[0]=='b')?1:0; p[n].m=i; p[n].f=0; p[n].n=l; p[n+1].m=i; p[n+1].f=1; p[n+1].n=r; n+=2; } sort(p,p+n,cmp); num=0; for(int i=0; i<n; i++) { int mm=p[i].m; if(i==0 || p[i].n!=p[i-1].n) p[num++]=p[i]; if(p[i].f) s[mm].r=num; else s[mm].l=num; } // for(int i=0; i<num; i++) printf("%d ",p[i].n); printf("\n"); // for(int i=0; i<m; i++) printf("[%d,%d] %d\n",s[i].l,s[i].r,s[i].v); solve(); } return 0; }