POJ 2777(Count Color) 经典染色问题
题目大意:就是给出两种指令操作,
1. "C A B C" Color the board from segment A to segment B with color C.
2. "P A B" Output the number of different colors painted between segment A and segment B (including).
题目分析:首先建树,树节点有三个域,分别为左右端点r,l,与代表颜色的color。首先声明,当color等于0的时候表示被多种颜色覆盖了。
更新:首先如果在查找的过程中遇到当前区间的颜色大于0,也就是说这个区间已经被完全染色过,那么就把这个区间的颜色值往下传递,然后把这个区间的颜色值color赋值为0,继续往下查找,也就是常规的三种情况:左子树,右子树,或者左右子树混合的
直到当前区间的跟我的所要更新的区间吻合,停止往下更新!进行染色,也就是把color值附给他,还有一种情况就是:当前区间的color值大于0并且等于我所要染色的颜色值,这样也可以停止往下更新!
计数:在往下计数的时候,如果遇到color值大于0的直接把他在hash[]表里的映射赋值为1,这样防止重复计数!如果color==0;就继续往下计数。又是常规的三种情况!
源程序+部分注释:
#include<iostream> #include<string.h> #include<algorithm> #include<stdio.h> using namespace std; struct node{ int a,b,color; }s[400000]; int h[60]; int Build(int x,int y,int num){ s[num].a=x; s[num].b=y; s[num].color=1; if(x==y) return 0; else{ Build(x,(x+y)/2,num+num); Build((x+y)/2+1,y,num+num+1); } } int modify(int x,int y,int num,int color){ if(x==s[num].a&&y==s[num].b){ s[num].color=color; return 0; } if(s[num].color==color) return 0; if(s[num].color>0){ s[num+num].color=s[num+num+1].color=s[num].color; s[num].color=0; } if(y<=(s[num].a+s[num].b)/2) modify(x,y,num+num,color); else if(x>(s[num].a+s[num].b)/2) modify(x,y,num+num+1,color); else{ modify(x,(s[num].a+s[num].b)/2,num+num,color); modify((s[num].a+s[num].b)/2+1,y,num+num+1,color); } } int getsum(int x,int y,int num){ if(s[num].color>0){ h[s[num].color]=1; return 0; } else{ if(y<=(s[num].a+s[num].b)/2) getsum(x,y,num+num); else if(x>(s[num].a+s[num].b)/2) getsum(x,y,num+num+1); else{ getsum(x,(s[num].a+s[num].b)/2,num+num); getsum((s[num].a+s[num].b)/2+1,y,num+num+1); } } } int main(){ int l,t,cas,i,j,sum; int x,y,color; char c; scanf("%d %d %d",&l,&t,&cas); memset(h,0,sizeof(h)); Build(1,l,1); for(j=1;j<=cas;j++){ sum=0; cin>>c; if('P'==c){ scanf("%d %d",&x,&y); getsum(x,y,1); for(i=1;i<=t;i++) cout<<h[i]; cout<<endl; for(i=1;i<=t;i++) sum+=h[i]; printf("%d\n",sum); memset(h,0,sizeof(h)); } else if('C'==c){ scanf("%d %d %d",&x,&y,&color); modify(x,y,1,color); } } return 0; }
HDU 1394(Minimum Inversion Number)
题目大意:给你一个连续的自然数序列,
a1, a2, ..., an-1, an (where m = 0 - the initial seqence)
a2, a3, ..., an, a1 (where m = 1)
a3, a4, ..., an, a1, a2 (where m = 2)
...
an, a1, a2, ..., an-1 (where m = n-1)
中逆序数最大的是多少。
其实用树状数组求这个是非常方便速度的,但是为了锻炼线段树 我还是用线段树写了。
程序+部分注释:
#include<iostream> #include<stdio.h> using namespace std; int ans=0; struct node{ int a,b; int v; }s[20000]; int t[5002]; int Build(int x,int y,int num){ s[num].a=x; s[num].b=y; s[num].v=0; if(x==y) return 0; Build(x,(s[num].a+s[num].b)/2,num+num); Build((s[num].a+s[num].b)/2+1,y,num+num+1); } int modify(int num,int ide ){ if(s[num].a==s[num].b){ s[num].v=1; return 0; } else{ if(ide<=(s[num].a+s[num].b)/2) modify(num+num,ide); else if(ide>(s[num].a+s[num].b)/2) modify(num+num+1,ide); s[num].v=s[num+num].v+s[num+num+1].v; } } int getsum(int x,int y,int num){ if(x==s[num].a&&y==s[num].b){ ans+=s[num].v; return 0; } else{ if(y<=(s[num].a+s[num].b)/2) getsum(x,y,num+num); else if(x>(s[num].a+s[num].b)/2) getsum(x,y,num+num+1); else{ getsum(x,(s[num].a+s[num].b)/2,num+num); getsum((s[num].a+s[num].b)/2+1,y,num+num+1); } } } int main(){ int n,i,min; while(scanf("%d",&n)!=EOF){ ans=0; for(i=1;i<=n;i++) scanf("%d",&t[i]); Build(1,n,1); for(i=n;i>=1;i--){ getsum(1,t[i]+1,1); modify(1,t[i]+1); 求出总和 } min=ans; for(i=1;i<=n-1;i++){ ans=ans-t[i]+n-t[i]-1; //因为数组是连续的自然数,从0到n所以 //t[i]移动到最后面去,就减少了t[i]个逆 //序对,同时也与以前是后面的而且比他大的数构成了 //N-(t[i]+1)组逆序对 if(ans<min) min=ans; } printf("%d\n",min); } return 0; }
HDU 1698(Just a Hook)
题目大意:给一组棍子染色,不同的颜色有不同的值,执行一系列的区间染色后,问这组棍子的总值是多少。
题目分析:建树:节点的域有左右节点和颜色,a,b,v;v=0时表示这个区间由多种颜色覆盖。
更新的时候,如果更新区间比当前区间小,并且当前区间的颜色大于0,说明已经被完全染色,就把该区间颜色传递到左右子树上,该区间染色为0,然后根据更新区间的大小,在左右子树上去搜索。
求和的时候遇到区间的v>0的时候说明完全被染色过了,就把区间的长度乘以颜色的值加到总和里面去,然后结束这个方向的递归查询,
源程序+部分注释:
#include<stdio.h> #include<iostream> using namespace std; struct node{ int a,b,v; }s[400000]; int t[150000],ans; void Build(int x,int y,int num){ s[num].a=x; s[num].b=y; s[num].v=1; if(x==y) return ; else{ Build(x,(x+y)/2,num+num); Build((x+y)/2+1,y,num+num+1); } } void modify(int x,int y,int num,int value){ if(x==s[num].a&&s[num].b==y){ s[num].v=value; return ; } if(s[num].v==value) return ; if(s[num].v>0){ s[2*num].v=s[2*num+1].v=s[num].v; s[num].v=0; //值往下传递。0代表混合染色 } if(y<=(s[num].a+s[num].b)/2){ modify(x,y,num+num,value); } else if(x>(s[num].a+s[num].b)/2) modify(x,y,num+num+1,value); else{ int mid=(s[num].a+s[num].b)/2; modify(x,mid,num+num,value); modify(mid+1,y,num+num+1,value); } } int getsum(int x,int y,int num){ if(s[num].v>0){ ans+=(s[num].b-s[num].a+1)*s[num].v; return 0; } else{ if(y<=(s[num].a+s[num].b)/2) getsum(x,y,num+num); else if(x>(s[num].a+s[num].b)/2) getsum(x,y,num+num+1); else{ getsum(x,(s[num].a+s[num].b)/2,num+num); getsum((s[num].a+s[num].b)/2+1,y,num+num+1); } } } int main(){ int cas,n,k,i,v; int x,y,count; cin>>cas; k=1; while(cas--){ ans=0; cin>>n; for(i=1;i<=n;i++){ t[i]=1; } Build(1,n,1); scanf("%d",&count); for(i=1;i<=count;i++){ scanf("%d %d %d",&x,&y,&v); modify(x,y,1,v); } getsum(1,n,1); printf("Case %d: The total value of the hook is %d.\n",k++,ans); } return 0; }
hdu 1754(I HATE IT)
题目分析:学生ID编号分别从1编到N。
第二行包含N个整数,代表这N个学生的初始成绩,其中第i个数代表ID为i的学生的成绩。
接下来有M行。每一行有一个字符 C (只取'Q'或'U') ,和两个正整数A,B。
当C为'Q'的时候,表示这是一条询问操作,它询问ID从A到B(包括A,B)的学生当中,成绩最高的是多少。
当C为'U'的时候,表示这是一条更新操作,要求把ID为A的学生的成绩更改为B。
有一次参加农大月赛,因为对线段树的求区间最值的更新还是不熟练,所以出错了!缅怀!
#include<stdio.h> int Max(int a,int b) { return a>b?a:b; } struct node{ int max,a,b; }s[800000]; int t[200010]; void Build(int x,int y,int num){ s[num].a=x; s[num].b=y; if(x==y){ s[num].max=t[x]; return ; } else{ Build(x,(x+y)/2,num+num); Build(((x+y)/2)+1,y,num+num+1); s[num].max=Max(s[num+num].max,s[num+num+1].max); } } int Query_max(int x,int y,int idx) { if(x<=s[idx].a && s[idx].b<=y) { return s[idx].max; } if(y<=s[idx*2].b) return Query_max(x,y,idx*2); else if(x>=s[idx*2+1].a) return Query_max(x,y,idx*2+1); else return Max(Query_max(x,s[idx*2].b,idx*2),Query_max(s[idx*2+1].a,y,idx*2+1)); } int modify(int x,int y,int num){ if(s[num].a==x&&s[num].b==x){ s[num].max=y; return 0; } else{ if(x<=s[num+num].b) modify(x,y,num+num); else modify(x,y,num+num+1); s[num].max=Max(s[num+num].max,s[num+num+1].max); } } int main(){ int n,cas,i,x,y; char c; while(scanf("%d %d",&n,&cas)!=EOF){ for(i=1;i<=n;i++) scanf("%d",&t[i]); Build(1,n,1); while(cas--){ getchar(); scanf("%c %d %d",&c,&x,&y); if(c=='Q'){ printf("%d\n",Query_max(x,y,1)); } else { modify(x,y,1); } } } return 0; }
HDU 1166(敌兵布阵)
题目大意:(1) Add i j,i和j为正整数,表示第i个营地增加j个人(j不超过30)
(2)Sub i j ,i和j为正整数,表示第i个营地减少j个人(j不超过30);
(3)Query i j ,i和j为正整数,i<=j,表示询问第i到第j个营地的总人数;
(4)End 表示结束,这条命令在每组数据最后出现;
题目分析:目的是纪念我线段树的开端,也是为了今后的回顾。其实就是基本操作,更新跟建树。
源程序+部分注释:
#include<iostream> #include<stdio.h> #include<string.h> using namespace std; struct node{ int sum,a,b; }s[140000]; int t[52005],ans; void Build(int x,int y,int num){ s[num].a=x; s[num].b=y; if(x==y) s[num].sum=t[x]; else{ Build(x,(x+y)/2,num+num); Build(((x+y)/2)+1,y,num+num+1); s[num].sum=s[num+num].sum+s[num+num+1].sum; } } void Query(int x,int y,int num){ if(x<=s[num].a&&y>=s[num].b) ans+=s[num].sum; else{ if(x>(s[num].a+s[num].b)/2) Query(x,y,num+num+1); else if(y<=(s[num].a+s[num].b)/2) Query(x,y,num+num); else{ Query(x,y,num+num); Query(x,y,num+num+1); } } } void Add(int x,int y,int num){ s[num].sum+=y; if(s[num].a==x&&s[num].b==x) return ; else{ if(x<=(s[num].a+s[num].b)/2) Add(x,y,num+num); else Add(x,y,num+num+1); } } int Sub(int x,int y,int num){ s[num].sum-=y; if(s[num].a==x&&s[num].b==x) return 0; else{ if(x<=(s[num].a+s[num].b)/2) Sub(x,y,num+num); else Sub(x,y,num+num+1); } } int main(){ int cas,n,i,k,x,y; char c[20]; scanf("%d",&cas); k=1; while(cas--){ scanf("%d",&n); for(i=1;i<=n;i++){ scanf("%d",&t[i]); } Build(1,n,1); cout<<"Case "<<k<<":"<<endl; k++; while(1){ scanf("%s",c); if(c[0]=='E') break; scanf("%d %d",&x,&y); if(c[0]=='Q'){ ans=0; Query(x,y,1); cout<<ans<<endl; } else if(c[0]=='A'){ Add(x,y,1); } else{ if(c[0]=='S') Sub(x,y,1); } } } return 0; }
HDU 2852(KiKi's K-Number)
#include<iostream> #include<stdio.h> #include<string.h> #include<algorithm> #define max 100000 #define lowbit(x) (x&(-x)) using namespace std; const int N=100001; int c[100005],a[100005]; int getsum(int x){ int sum=0; while(x){ sum+=c[x]; x-=lowbit(x); } return sum; } int modify(int x,int num){ while(x<=N){ c[x]+=num; x+=lowbit(x); } return 0; } int main(){ int n,i,left,right,md; int sign,m,t,num1; while(scanf("%d",&n)!=EOF){ memset(c,0,sizeof(c)); memset(a,0,sizeof(a)); while(n--){ scanf("%d",&sign); if(sign==0){ scanf("%d",&t); a[t]++; modify(t,1); } else if(sign==1){ scanf("%d",&t); if(!a[t]) cout<<"No Elment!"<<endl; else{ a[t]--; modify(t,-1); } } else if(sign==2){ scanf("%d %d",&m,&t); num1=getsum(m)+t; if(num1>getsum(max)){ cout<<"Not Find!"<<endl; continue; } left=m+1; right=max; while(right-left>1){ md=getsum((right+left)/2); if(md>num1){ right=(right+left)/2; } else if(md<num1){ left=(right+left)/2; } else { if(a[(right+left)/2]){ cout<<(right+left)/2<<endl; break; } else right=(right+left)/2; } } if(right-left<=1){ if(getsum(left)>=num1) cout<<left<<endl; else cout<<right<<endl; } } } } return 0; }
HDU 1828(Picture)经典求矩形的并周长
题目大意:给出对角线让您求所有矩形构成整体的并周长。
解题分析:这里跟求面积所要求的基本操作是一致的。对Y进行离散化,然后建树,树节点里比起求并面积的多了几个域,一个是记录左右端点被覆盖的次数lb,rb,一个是记录区间内连续子段的个数。
通过上面的记录更新我们可以这样计算:
1)通过Y的长度就是,把这次在Y轴上的投影与上次的取差的绝对值,这就是它在Y上面的增量,把这些增量求和,这样就解决了Y上的周长问题!
2)因为线段树的节点里已经计算出目前该区间的连续子区间的个数,所以在这里就可以计算周长在X上的长度,因为Y上的段树就表明有该区间X的元线段要计算多少次,计算公式:sum+=2*(t[i].x-t[i-1].x)*lastn;在这里lastn表示上一次计算出的Y上投影的区间个数,也就是我这次计算所涉及的!
代码程序如下:
#include<iostream> #include<stdio.h> #include<algorithm> #include<math.h> using namespace std; int ym[10200];//注意区间要开大点 不然RE struct Tree{ int li,ri,lb,rb;//li,ri左右端点的值 int lenth,ans,cover;//lenth表示被覆盖至少一次的长度 //ans记录子区间个数,lb,rb记录左右端点被覆盖次数 //cover记录整个区间被覆盖的次数 }s[50000]; struct node{ int x,y1,y2; int flag; }t[50000];//表示左,右边的节点 int cmp1(int x,int y){ return x<y; } int cmp2(struct node st,struct node sd){ if(st.x!=sd.x) return st.x<sd.x; else return st.flag>sd.flag; //这里要非常注意,因为要计算并周长, //当两个边x一样的时候,要将左边先插入! } int len(int num){ if(s[num].cover>0) s[num].lenth=s[num].ri-s[num].li; else s[num].lenth=s[num+num].lenth+s[num+num+1].lenth; } int slen(int num){ if(s[num].cover>0) s[num].ans=1; else { s[num].ans=s[num+num].ans+s[num+num+1].ans; if(s[num+num].rb!=0&&s[num+num+1].lb!=0) s[num].ans--; //左子树的右端点如果跟右子树的左端点都被覆盖了 //说明在整个区间里,有一个区间横跨左右子树, //但被重复计算了,所以要减去1! } } int Build(int x,int y,int num){ s[num].li=ym[x]; s[num].ri=ym[y]; s[num].lenth=0; s[num].cover=s[num].rb=s[num].lb=s[num].ans=0; if(x+1==y) return 0; int mid=(x+y)>>1; Build(x,mid,num+num); Build(mid,y,num+num+1); } int modify(int num,struct node st){ if(st.y1==s[num].li) s[num].lb+=st.flag; if(st.y2==s[num].ri) s[num].rb+=st.flag; if(st.y1==s[num].li&&st.y2==s[num].ri){ s[num].cover+=st.flag; } else{ if(st.y1>=s[num+num].ri) modify(num+num+1,st); else if(st.y2<=s[num+num+1].li) modify(num+num,st); else{ struct node sd=st; sd.y2=s[num+num].ri; modify(num+num,sd); sd=st; sd.y1=s[num+num+1].li; modify(num+num+1,sd); } } len(num); slen(num); } int main(){ int cas,i,j; int x1,x2,y1,y2; int sum=0; while(scanf("%d",&cas)!=EOF){ sum=0; for(j=1,i=1;i<=cas;i++,j+=2){ scanf("%d %d %d %d",&x1,&y1,&x2,&y2); ym[j]=y1; t[j].x=x1; t[j].y1=y1; t[j].y2=y2; t[j].flag=1; ym[j+1]=y2; t[j+1].x=x2; t[j+1].y1=y1; t[j+1].y2=y2; t[j+1].flag=-1; } sort(ym+1,ym+j,cmp1); sort(t+1,t+j,cmp2); Build(1,j-1,1); int lastn=0,lasts=0; //lasts 表示上次Y上次的投影长度,lastn //表示Y上次的投影连续区间的个数 //其实连续区间就是指图形从上到下有多少个不相连的部分 //因为求周长,所以有多少个部分就要把X的区间长度 //乘以这个区间个数,因为他们每个部分的上下边不重叠 for(i=1;i<j;i++){ modify(1,t[i]); sum+=abs(lasts-s[1].lenth); if(i!=1){ sum+=lastn*(t[i].x-t[i-1].x)*2; } lastn=s[1].ans; //当初我答案一直错,检查了很久,最后终于发现是 //把lastn=s[1].ans;放在了if语句里面,这样的话影响了第2次的计算。所以周长计算出来会有点偏小,因为我第二次的lastn还是=0,应该是等于1,呵呵 lasts=s[1].lenth; } printf("%d\n",sum); } }
HDU 1255 (覆盖的面积) 典型的求交面积
题目大意:给对角线 求覆盖次数至少为2次的面积
思路:首先把Y离散化,从小到大排序,然后以Y在数组种存放的位置,建树。但是线段树的lf,rf,存放的是Y的实际值,这就是离散化。
然后 在节点里用cover记录被覆盖的次数,lenth表示至少被覆盖一次的区间长度,inlenths,记录被覆盖两次以上的区间长度,这里比起求矩形的并面积,就是多了个记录与更新至少覆盖两次的情况的操作!
建树:跟求区间的并 区别不大,就是多了一个对inlenths的初始化。
更新:从上到下遍历,找到需要更新区间,如果是矩形的左边,cover加1,表示在这个高度,多了个区间覆盖在其之上,如果是矩形的右边则减1,说明,少了个区间覆盖在这个高度范围内,也就是矩形的覆盖,更新当前区间的lenth,如果cover>0则lenth等于该区间的整体长度,否则等于左右子树的覆盖长度,然后更新inlenths的长度,当cover>2,表明这个高度有2个以上矩形覆盖,即把inlenths赋值为当前区间的整体长度,如果cover=1说明这个区间整体只被覆盖一次,那么inlenths的值就等于左右子区间被覆盖一次以上的长度,因为整体的已经被覆盖了一次,只有子区间有被覆盖过大于等于一次,那么该长度也被覆盖了2次以上!,最后当cover=0,那么说明这个区间没有整体被覆盖过,那么inlenths等于左右子区间的inlenths的长度!最后结束这次的遍历。如果没有到达更新区间,那么就分三种情况,在左子树,右子树,左右子树混合的情况,分别考虑,然后每次都会往上更新lenth和inlenths的值!
源程序代码+部分注释:
#include<iostream> #include<stdio.h> #include<math.h> #include<algorithm> #define esp (1e-8) using namespace std; double x_1,y_1,x_2,y_2,ym[30000],sum; int i,j,n,cas; struct tree{ double lf,rf,lenth,inlenths; int cover; }s[80000]; //这里数组开的小了当初,WA了好多次 //所以一气之下开的很大! struct node{ double x,y1,y2; int flag; }t[30000]; void len(int num){ if(s[num].cover>0) s[num].lenth=s[num].rf-s[num].lf; else s[num].lenth=s[num+num].lenth+s[num+num+1].lenth; } void inlen(int num){ if(s[num].cover>1)//表示整个区间被覆盖次数大于等于2 s[num].inlenths=s[num].rf-s[num].lf; else if(s[num].cover==1) s[num].inlenths=s[num+num].lenth+s[num+num+1].lenth; //整个区间只被覆盖一次,如果子区间有被覆盖大于等于1次,说明该子段的长度, //为被覆盖至少2次的长度 else s[num].inlenths=s[num+num].inlenths+s[num+num+1].inlenths; //整个区间都没被覆盖,该区间覆盖2次以上的长度就等于 //他左右子树被覆盖的长度的和 } int cmp1(double a,double b){ return a<b; } int cmp2(struct node a,struct node b){ return a.x<b.x; } int Build(int x,int y,int num){ s[num].lf=ym[x]; s[num].rf=ym[y]; s[num].cover=s[num].lenth=s[num].inlenths=0; if(x+1==y) return 0; int mid=(x+y)/2; Build(x,mid,num+num); Build(mid,y,num+num+1); } int modify(int num,struct node n){ if(fabs(s[num].lf-n.y1)<=esp&&fabs(s[num].rf-n.y2)<=esp){ s[num].cover+=n.flag; len(num); inlen(num); return 0; } //以下是分三种情况进行讨论 if(n.y1>=s[num+num].rf) modify(num+num+1,n); else if(n.y2<=s[num+num+1].lf) modify(num+num,n); else{ struct node sn=n; sn.y2=s[num+num].rf; modify(num+num,sn); sn=n; sn.y1=s[num+num+1].lf; modify(num+num+1,sn); } len(num); inlen(num); } int main(){ scanf("%d",&cas); while(cas--){ sum=0; scanf("%d",&n); for(i=1,j=1;i<=n;i++,j+=2){ scanf("%lf %lf %lf %lf", &x_1, &y_1, &x_2, &y_2); ym[j]=y_1; t[j].x=x_1; t[j].y1=y_1; t[j].y2=y_2; t[j].flag=1; ym[j+1]=y_2; t[j+1].x=x_2; t[j+1].y1=y_1; t[j+1].y2=y_2; t[j+1].flag=-1; } sort(ym+1,ym+j,cmp1); sort(t+1,t+j,cmp2); Build(1,j-1,1); modify(1,t[1]); for(i=2;i<j;i++){ sum+=s[1].inlenths*(t[i].x-t[i-1].x); modify(1,t[i]); } printf("%.2lf\n",sum); //在我的编译器上好像样例的答案为7.62但是可以过 //我不知道有没有更好的办法能够四舍五入保留两位小数 } return 0;
HDU 1542 Atlantis(线段的树应用——求矩形的面积并)
题目大意:给定每个矩形的对角线的两个端点,让你求这些矩形的面积的并集,即重叠的不能重复计算!
题目分析:这题就是典型的线段树求面积并!
离散化:对所有节点的Y进行升序排序,然后以Y的位置建树,就是指,在线段树里面,左右节点的实际意义就是指这个线段在Y的升序数组里的位置,但是我们把lf,rf赋值为这个线段左右端点的具体值!这就是离散化!
建树的细节:树的每个节点有lf,rf,cover,lenth,分别指这个节点所表示的线段的左端点,右端点,被覆盖的次数(在这里覆盖的次数是为了 区分重叠矩形的情况下,高度的正确存取,因为一个矩形对Y轴的覆盖是以他的左边开始,右边结束的,所以当插入左边的时候,我们把cover+1,右边的时候把cover-1,说明这个矩形对Y的覆盖已经结束,计算下一个矩形时,不会错误的把我这个矩形的高度当作下一个矩形的高度!),lenth指这个区间被矩形覆盖的长度即矩形的实际高度!;
插入的时候,先把存放节点的数组,对X进行升序排序!然后顺次插入,分别计算!
代码如下+部分注释:
#include<iostream> #include<algorithm> #include<stdio.h> using namespace std; struct tree{ double lf,rf,lenth; int cover; }s[4000];//树节点定义 double x1,y1,x2,y2,ym[1000]; struct node{ double x,y1,y2; int flag; }t[4000];//存放矩形左,右边的节点 int cmp1(double x,double y){ return x<y; } int cmp2(struct node a,struct node b){ return a.x<b.x; } void len(int n){ if(s[n].cover>0) s[n].lenth=s[n].rf-s[n].lf; else s[n].lenth=s[2*n+1].lenth+s[2*n].lenth; } int Build(int x,int y,int num){ s[num].lf=ym[x]; s[num].rf=ym[y]; s[num].lenth=s[num].cover=0; if(x+1==y) return 0; int mid=(x+y)/2; Build(x,mid,num+num); Build(mid,y,num+num+1); } int modify(int num,struct node n){ if(s[num].lf==n.y1&&s[num].rf==n.y2){ s[num].cover+=n.flag; len(num); return 0; } if(n.y1>=s[num+num].rf) modify(num+num+1,n); else if(n.y2<=s[num+num+1].lf) modify(num+num,n); else{ struct node vs=n; vs.y2=s[num+num].rf; modify(num+num,vs); vs=n; vs.y1=s[num+num+1].lf; modify(num+num+1,vs); } len(num);//往上更新线段被覆盖的实际长度!如果节点的cover>0 该区间节点的被覆盖就是,区间的整体程度,否则,该区间节点的被覆盖等于左右节点被覆盖长度的和! } int main(){ int cas,i,j,k=1; double sum; while(cin>>cas,cas){ for(j=i=1;i<=cas;i++,j+=2){ scanf("%lf %lf %lf %lf",&x1,&y1,&x2,&y2); ym[j]=y1; t[j].x=x1; t[j].y1=y1; t[j].y2=y2; t[j].flag=1; ym[j+1]=y2; t[j+1].x=x2; t[j+1].y1=y1; t[j+1].y2=y2; t[j+1].flag=-1; } sort(ym+1,ym+j,cmp1); sort(t+1,t+j,cmp2); Build(1,j-1,1); modify(1,t[1]); sum=0; for(i=2;i<j;i++){ sum+=(t[i].xt[i-1].x)*s[1].lenth; //每插入完一个点,就以他后继的点求面积! modify(1,t[i]); } printf("Test case #%d\n",k); printf("Total explored area: %.2lf\n\n",sum); k++; } return 0; }
POJ 2528(Mayor's posters)经典线段树问题
题目大意:给出N个海报的左右边界,然后按顺序贴完这些海报,要求计算没有完全被覆盖的海报的张数。
题目分析:首先离散化:按顺序用数组把左右边界存起来,然后升序排序,去除重复出现的,最后用左右边界在该数组出现的位置作为左右边界的值;还有一个就是,计算的时候可以当作一个染色过程,倒着染,您可以自己想想一下,这样才能够使得结果不被后面的染色操作干扰,不是么!因为后面计算的都是先贴的,根本不会对我产生影响的!
#include<iostream> #include<stdio.h> #include<algorithm> #include<string.h> #define MAX 10005 using namespace std; struct node{ int a,b,color,flag; }s[8*MAX]; struct st{ int x,y; }t[2*MAX]; int N[2*MAX],vs[2*MAX],sim[4*MAX],times,ans; int cmp(int a,int b){ return a<b; } int getidex(int y){ int i=1,j=times+1,mid; while(i<=j){ mid=(i+j)/2; if(N[mid]==y) return mid; if(N[mid]>y) j=mid; else i=mid+1; } return 0; } int Build(int x,int y,int num){ s[num].color=0; s[num].flag=0; s[num].a=x; s[num].b=y; if(x==y) return 0; int mid=(x+y)/2; Build(x,mid,num+num); Build(mid+1,y,num+num+1); } int modify(int x,int y,int num,int color){ if(s[num].color>0) //说这个区间会被覆盖,不用再讨论了! return 0; if(x==s[num].a&&y==s[num].b){//找到查询区间 if(s[num].flag==0){//判断是否被完全覆盖 s[num].color=color; s[num].flag=1;//染色 if(!vs[color]){//如果这个颜色没有出现过,计数! //先前我这里忘记考虑了,其实这个也许是这次查询的一个子区间 //这里是对其中一个子区间进行染色,计数,当然,其他的子区间肯定也染色! //但是计数只需要一次!! ans++; vs[color]=1; } } return 0; } int mid=(s[num].a+s[num].b)/2; if(y<=mid) modify(x,y,num+num,color); else if(x>mid) modify(x,y,num+num+1,color); else{ modify(x,mid,num+num,color); modify(mid+1,y,num+num+1,color); } //重点注意一下这里:如果左右子树都被染色了 //很显然要往上更新父亲节点的染色状态即flag! if(s[num+num].flag&&s[num+num+1].flag) s[num].flag=1; } int main(){ int cas,n,i,j; scanf("%d",&cas); while(cas--){ memset(vs,0,sizeof(vs)); ans=0; scanf("%d",&n); for(i=1;i<=n;i++) scanf("%d%d",&t[i].x,&t[i].y); //把左右边界的存放在sim[]里面一边,以便下面的离散操作! for(i=1;i<=n;i++){ sim[i]=t[i].x; } for(i=n+1,j=1;j<=n;j++,i++){ sim[i]=t[j].y; } sort(sim+1,sim+1+2*n,cmp);//升序排序 //去除重复的边界值 N[1]=sim[1]; times=1; for(i=2;i<=2*n;i++){ if(N[times]!=sim[i]) N[++times]=sim[i]; } Build(1,times,1); int color=0; for(i=n;i>=1;i--){ int sx=getidex(t[i].x); //以边界值在sim[]里面的位置,作为当前的边界值,这就是离散的核心思想! int sy=getidex(t[i].y); modify(sx,sy,1,++color); } printf("%d\n",ans); } return 0; }