zoukankan      html  css  js  c++  java
  • ●线段树的三个题(poj 3225,hdu 1542,hdu 1828)

    poj 3225 Help with Intervals(线段树区间问题)

    ○赘述题目

    给出以下集合操作:

    6%JIK~SQRBO8%%NDBCBVKD2

    然后有初始的一个空集S,和以下题目给出的操作指令,并输入指令:

    3}HTI{~IP1JS)C10MXZ]G~A

    要求进行指令操作后,按格式输出集合S;

    ○题解

    (此文标题就告诉了我们要用线段树维护。。。)

    关键难点:

    1.此题操作较复杂,如何较简便的进行线段树维护?

    看看这位大师的转化:

    8UWIQPC)E]ZM4}1F~DK7P8D

    。。。把各种操作转化为较为一致的区间修改后,线段树就能“开始表演”了。

    2.涉及到开闭区间,又如何

    xio习的大师的方法:

    比如输入的左右端点a,b

    若是[ ],则对应线段树的范围是 2a—2b

    若是( ],则对应线段树的范围是 2a+1—2b

    若是[ ),则对应线段树的范围是 2a—2b-1

    若是( ),则对应线段树的范围是 2a+1—2b-1

    仔细去想想其中的妙处吧。

    ○值得注意的几点

    1.注意覆盖操作和异或操作的关系:若有覆盖,则原来的异或标记失效;

    2.在把输入指令转化为线段树的区间操作时,要让各种操作有尽可能多的一致性,

    就如上面贴图中的方法,使得各种操作只用覆盖异或这两个操作就能实现,这样可以在很大程度上降低代码复杂度和查错时间。

    ○代码

    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include <algorithm>
    #define MAXN 131072
    #define lson u<<1,l,mid
    #define rson u<<1|1,mid+1,r 
    using namespace std;
    int cr[MAXN<<2];
    bool xr[MAXN<<2],vis[MAXN];
    void fx(int u)
    {
    	if(cr[u]!=-1) cr[u]^=1;
    	else xr[u]^=1;
    }
    void pushdown(int u)
    {
    	if(cr[u]!=-1) cr[u<<1]=cr[u<<1|1]=cr[u],xr[u<<1]=xr[u<<1|1]=0, cr[u]=-1; 
    	//cr[u]=-1 这一语句的解释: 
    	//因为要modify下面的子树(即更小的区间),无论此大区间是否会“鱼龙混杂”,但, 
    	//此处改为cr[u]=-1即不会影响答案(询问时只需要到较下层的线段树节点去找答案就好了)
    	//同时又规避了pushup。
    	else if(xr[u]) fx(u<<1),fx(u<<1|1),xr[u]=0;
    }
    void modify(int u,int l,int r,int al,int ar,char op)
    {
    	if(al<=l&&r<=ar)
    	{
    		if(op=='U') cr[u]=1,xr[u]=0;
    		else if(op=='D') cr[u]=0,xr[u]=0;
    		else if(op=='C'||op=='S') fx(u);
    		return;
    	}
    	pushdown(u);
    	int mid=(l+r)>>1;
    	if(al<=mid) modify(lson,al,ar,op);
    	else if(op=='I'||op=='C') cr[u<<1]=xr[u<<1]=0;
    	if(mid<ar) modify(rson,al,ar,op);
    	else if(op=='I'||op=='C') cr[u<<1|1]=xr[u<<1|1]=0;
    }
    void query(int u,int l,int r)
    {
    	if(cr[u]==1) {for(int i=l;i<=r;i++) vis[i]=1; return;}
    	else if(cr[u]==0) return;
    	pushdown(u);
    	int mid=(l+r)>>1;
    	query(lson);
    	query(rson);
    }
    int main()
    {
    	cr[1]=xr[1]=0;
    	char op,l,r; int a,b;
    	while(~(scanf("%c %c%d,%d%c
    ",&op,&l,&a,&b,&r)))
    	{
    		a<<=1;b<<=1;                                  //解决区间开闭问题 
    		if(l=='(') a++; if(r==')') b--;
    		if(a>b) {if(op=='I'||op=='C') cr[1]=xr[1]=0;} //T 为空集时的处理
    		else modify(1,0,MAXN,a,b,op); 
    	}
    	query(1,0,MAXN);
    	int s=-1,e; bool f=0;
    	for(int i=0;i<=MAXN;i++)
    	{
    		if(vis[i]) {if(s==-1) s=i; e=i;}
    		else 
    		{	
    			if(s!=-1)
    			{
    				if(f) printf(" "); f=true;
    				printf("%c%d,%d%c",s&1==1?'(':'[',s>>1,e+1>>1,e&1==1?')':']');
    				s=-1;
    			}	
    		}
    	}
    	if(!f)printf("empty set");
    	return 0;
    }
    

    ●hdu 1542 Atlantis(扫描线面积并)

    ○线段树的扫描线面积并,如今总算是入门了。

    ○没什么题解。。。

    ○贴上代码,当作模板,算是一种记录。

    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #define MAXN 2222
    #define lson u<<1,l,mid
    #define rson u<<1|1,mid+1,r 
    using namespace std;
    struct seg{
    	double x1,x2,y;
    	int p;
    }s[250];
    int cnt[MAXN<<2];
    double sum[MAXN<<2],x[MAXN],ans;
    bool cmp(seg a,seg b) {return a.y<b.y;}
    void pushup(int u,int l,int r)
    {
    	if(cnt[u]) sum[u]=x[r+1]-x[l];       //若[l-r]区间全部被覆盖,就直接计算 
    	//若[l-r]区间未被全部被覆盖
    	else if(l==r) sum[u]=0;				//底层处理 
    	else sum[u]=sum[u<<1]+sum[u<<1|1]; //............
    }
    void modify(int u,int l,int r,int al,int ar,int p)
    {
    	if(al<=l&&r<=ar)
    	{
    		cnt[u]+=p;
    		pushup(u,l,r);
    		return;
    	}
    	int mid=(l+r)>>1;
    	if(al<=mid) modify(lson,al,ar,p);
    	if(mid<ar) modify(rson,al,ar,p);
    	pushup(u,l,r);
    }
    int main()
    {
    	int n,cas=1,snt,lnt;
    	while(1)
    	{
    		scanf("%d",&n);
    		if(n==0) break;
    		lnt=snt=0;
    		ans=0;
    		double a,b,c,d;
    		for(int i=1;i<=n;i++)	
    		{
    			scanf("%lf%lf%lf%lf",&a,&b,&c,&d);
    			s[++snt]=(seg){a,c,b,1};
    			s[++snt]=(seg){a,c,d,-1};
    			x[++lnt]=a; x[++lnt]=c;
    		}
    		sort(x+1,x+lnt+1);
    		sort(s+1,s+snt+1,cmp);
    		int m=1,l,r; 
    		for(int i=2;i<=lnt;i++) if(x[i]!=x[i-1]) x[++m]=x[i];
    		for(int i=1;i<=snt;i++)
    		{
    			l=lower_bound(x+1,x+m+1,s[i].x1)-x;
    			r=lower_bound(x+1,x+m+1,s[i].x2)-x-1;//●●●●巧妙的减一 
    			modify(1,1,m,l,r,s[i].p); 
    			ans+=sum[1]*(s[i+1].y-s[i].y);
    		}//循环完后,相关数组已清空
    		printf("Test case #%d
    Total explored area: %.2lf
    
    ",cas++,ans);
    	}
    	return 0;
    }

    hdu 1828 Picture(扫描线周长并)

    ○和面积并差不多,但因为要求出和扫描线垂直的边的长度(即侧边),所以要多维护点东西来记录每次有几条侧边。

    ○特别注意排序的顺序与出边和入边有关了。

    ○模板来了:

    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #define MAXN 22222
    #define lson u<<1,l,mid
    #define rson u<<1|1,mid+1,r 
    using namespace std;
    struct seg{
    	double x1,x2,y;
    	int p;
    }s[MAXN];
    int cnt[MAXN<<2],bum[MAXN<<2];
    int sum[MAXN<<2],x[MAXN],ans;
    bool lb[MAXN<<2],rb[MAXN<<2];
    bool cmp(seg a,seg b) {return a.y==b.y?a.p>b.p:a.y<b.y;}
    void pushup(int u,int l,int r)
    {
    	if(cnt[u]) //若[l-r]区间全部被覆盖,就直接计算 
    	{
    	sum[u]=r-l+1;       
    	bum[u]=2;
    	lb[u]=rb[u]=1;
    	}
    	//若[l-r]区间未被全部被覆盖
    	else if(l==r) sum[u]=bum[u]=0,lb[u]=rb[u]=0;			//底层处理 
    	else 
    	{
    		sum[u]=sum[u<<1]+sum[u<<1|1];
    		lb[u]=lb[u<<1]; rb[u]=rb[u<<1|1];
    		bum[u]=bum[u<<1]+bum[u<<1|1];
    		if(rb[u<<1]&&lb[u<<1|1]) bum[u]-=2;
    	}
    }
    void modify(int u,int l,int r,int al,int ar,int p)
    {
    	if(al<=l&&r<=ar)
    	{
    		cnt[u]+=p;
    		pushup(u,l,r);
    		return;
    	}
    	int mid=(l+r)>>1;
    	if(al<=mid) modify(lson,al,ar,p);
    	if(mid<ar) modify(rson,al,ar,p);
    	pushup(u,l,r);
    }
    int main()
    {
    	int LL,RR,n,snt,lnt,lb,rb,last,ans=0;
    	while(~scanf("%d",&n))
    	{
    		ans=snt=0;LL=10000,RR=-10000;
    		int a,b,c,d;
    		for(int i=1;i<=n;i++)	
    		{
    			scanf("%d%d%d%d",&a,&b,&c,&d);
    			s[++snt]=(seg){a,c,b,1};
    			s[++snt]=(seg){a,c,d,-1};
    			LL=min(LL,a);
    			RR=max(RR,c);
    		}
    		sort(s+1,s+snt+1,cmp);
    		last=0; 
    		for(int i=1;i<=snt;i++)
    		{
    			int l=s[i].x1,r=s[i].x2;
    			modify(1,LL,RR-1,l,r-1,s[i].p);
    			ans+=bum[1]* (s[i+1].y-s[i].y);
    			ans+=abs(sum[1]-last);
    			last=sum[1];
    		}
    		printf("%d
    ",ans);
    	}
    	return 0;
    }

    ●Tips○总结几点(聪明人才看得到。。。)

    1.对于线段树的区间题,在把输入指令转化为线段树的区间操作时,要让各种操作有尽可能多的一致性,能使用更多相同的函数,这样可以在很大程度上降低代码复杂度和查错时间。

    2.扫描线时,离散化后任把点看成线段树的底层元素,未离散化则将单位线段看成线段树的底层元素。

    3.打线段树时一定要谨慎又谨慎,尽量减少出错,不然查错令人绝望!!!!!!

  • 相关阅读:
    MongoDB--CSharp Driver Quickstart .
    关于 IIS7.0下文件写入无权限的解决办法
    Android和WCF通信
    要想有什么样的成就就要有什么样的眼光
    DateTime.Now.ToString() 用法
    机械硬盘怎么看是否4k对齐
    【.Net】在WinForm中选择本地文件
    【.Net】C#获取Windows系统特殊文件夹的路径
    【Python】Python网络编程
    【Asp.Net Core】在Visual Studio 2017中使用Asp.Net Core构建Angular4应用程序
  • 原文地址:https://www.cnblogs.com/zj75211/p/7199652.html
Copyright © 2011-2022 走看看