线段树上线段果,线段树下你和我~~~~~~~在敲代码

这周刚学了线段树,学得一塌糊涂,还是写个blog来加深一下印象,看看会什么感悟吧QAQ。。

一、线段树

线段树(英语:Segment tree)是一种二叉树形数据结构,1977年由Jon Louis Bentley发明[1],用以储存区间线段,并且允许快速查询结构内包含某一点的所有区间。

一个包含n个区间的线段树,空间复杂度为O(n\log n),查询的时间复杂度则为{\displaystyle O(\log n+k)},其中k是符合条件的区间数量。

此数据结构亦可推广到高维度。(摘自维基百科)。

线段树利用二叉树不断二分的特点,实现了快速查找,修改点或区间的功能,实现维护区间。

所以我们利用线段树就可以解决一下几种基础问题:

(一)单点修改,单点查询

(二)单点修改,区间查询

(三)区间修改,单点查询

(四)区间修改,区间查询

这几种基础问题都是对修改函数和查询函数的考查。而一些进阶问题,如:

(一) 区间合并

(二)扫描线

(三)离线处理

(四)DFS序

(五)lazy标记次序

是对pushup和pushdown的考查。

解决线段树问题还要用到一些特殊技巧或者方法:离散化处理数据,状压,DP,剪枝。。。(头好大,想退休。。)

二、例题

贴几道例题。

1、HDU - 1166

C国的死对头A国这段时间正在进行军事演习,所以C国间谍头子Derek和他手下Tidy又开始忙乎了。A国在海岸线沿直线布置了N个工兵营地,Derek和Tidy的任务就是要监视这些工兵营地的活动情况。由于采取了某种先进的监测手段,所以每个工兵营地的人数C国都掌握的一清二楚,每个工兵营地的人数都有可能发生变动,可能增加或减少若干人手,但这些都逃不过C国的监视。 
中央情报局要研究敌人究竟演习什么战术,所以Tidy要随时向Derek汇报某一段连续的工兵营地一共有多少人,例如Derek问:“Tidy,马上汇报第3个营地到第10个营地共有多少人!”Tidy就要马上开始计算这一段的总人数并汇报。但敌兵营地的人数经常变动,而Derek每次询问的段都不一样,所以Tidy不得不每次都一个一个营地的去数,很快就精疲力尽了,Derek对Tidy的计算速度越来越不满:"你个死肥仔,算得这么慢,我炒你鱿鱼!”Tidy想:“你自己来算算看,这可真是一项累人的工作!我恨不得你炒我鱿鱼呢!”无奈之下,Tidy只好打电话向计算机专家Windbreaker求救,Windbreaker说:“死肥仔,叫你平时做多点acm题和看多点算法书,现在尝到苦果了吧!”Tidy说:"我知错了。。。"但Windbreaker已经挂掉电话了。Tidy很苦恼,这么算他真的会崩溃的,聪明的读者,你能写个程序帮他完成这项工作吗?不过如果你的程序效率不够高的话,Tidy还是会受到Derek的责骂的. 

Input

第一行一个整数T,表示有T组数据。 
每组数据第一行一个正整数N(N<=50000),表示敌人有N个工兵营地,接下来有N个正整数,第i个正整数ai代表第i个工兵营地里开始时有ai个人(1<=ai<=50)。 
接下来每行有一条命令,命令有4种形式: 
(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 表示结束,这条命令在每组数据最后出现; 
每组数据最多有40000条命令 

Output

对第i组数据,首先输出“Case i:”和回车, 
对于每个Query询问,输出一个整数并回车,表示询问的段中的总人数,这个数保持在int以内。 

Sample Input

1
10
1 2 3 4 5 6 7 8 9 10
Query 1 3
Add 3 6
Query 2 7
Sub 10 2
Add 6 3
Query 3 10
End 

Sample Output

Case 1:
6
33
59

比较基础的单点修改区间查询。

#include<cstdio>
#include<cstring>
const int maxn=5e4+10;
int sum[4*maxn];
int a[maxn];
void Pushup(int cnt)
{
	sum[cnt]=sum[cnt<<1]+sum[cnt<<1|1];
}
void Build(int l,int r,int cnt)
{
	if(l==r)
	{
		sum[cnt]=a[l];
		return;
	}
	int mid=(l+r)>>1;
	Build(l,mid,cnt<<1);
	Build(mid+1,r,cnt<<1|1);
	Pushup(cnt);
}
void Update(int p,int c,int l,int r,int cnt)
{
	if(l==r)
	{
		sum[cnt]+=c;
		return;
	}
	int mid = (l+r)>>1;
	if(p<=mid)
		Update(p,c,l,mid,cnt<<1);
	else
		Update(p,c,mid+1,r,cnt<<1|1);
	Pushup(cnt);
}
int Query(int L,int R,int l,int r,int cnt)
{
	if(L<=l&&r<=R)
		return sum[cnt];
	int mid=(l+r)>>1;
	int ans=0;
	if(L<=mid)
		ans+=Query(L,R,l,mid,cnt<<1);
	if(R>mid)
		ans+=Query(L,R,mid+1,r,cnt<<1|1);
	return ans;
}

int main()
{
	int t;
	scanf("%d",&t);
	int cas=1;
	while(t--)
	{
		int n;
		scanf("%d",&n);
		for(int i=1;i<=n;i++)
			scanf("%d",&a[i]);
		Build(1,n,1);
		printf("Case %d:\n",cas++);
		char s[10];
		while(1)
		{
			scanf("%s",s);
			int x,y;
			if(strcmp(s,"Query")==0)
			{
				scanf("%d %d",&x,&y);
				printf("%d\n",Query(x,y,1,n,1));
			}
			else if(strcmp(s,"Add")==0)
			{
				scanf("%d %d",&x,&y);
				Update(x,y,1,n,1);
			}
			else if(strcmp(s,"Sub")==0)
			{
				scanf("%d %d",&x,&y);
				Update(x,-y,1,n,1);
			}
			else if(strcmp(s,"End")==0)
				break;
		}
	}
}

2、区间合并问题(坐稳了,加速了)

HDU - 1540

uring the War of Resistance Against Japan, tunnel warfare was carried out extensively in the vast areas of north China Plain. Generally speaking, villages connected by tunnels lay in a line. Except the two at the ends, every village was directly connected with two neighboring ones. 

Frequently the invaders launched attack on some of the villages and destroyed the parts of tunnels in them. The Eighth Route Army commanders requested the latest connection state of the tunnels and villages. If some villages are severely isolated, restoration of connection must be done immediately! 

Input

The first line of the input contains two positive integers n and m (n, m ≤ 50,000) indicating the number of villages and events. Each of the next m lines describes an event. 

There are three different events described in different format shown below: 

D x: The x-th village was destroyed. 

Q x: The Army commands requested the number of villages that x-th village was directly or indirectly connected with including itself. 

R: The village destroyed last was rebuilt. 

Output

Output the answer to each of the Army commanders’ request in order on a separate line. 

Sample Input

7 9
D 3
D 6
D 5
Q 4
Q 5
R
Q 4
R
Q 4

Sample Output

1
0
2
4

题意:有N个点,开始状态是好的,有M次操作,有三种操作,D操作破坏一个点,R操作修复一个刚刚被破坏的点,Q操作查询一个点周围有多少是好的。

理解区间合并题型的话,这就是模板题吧,简单说下区间合并。

区间合并问题是解决01串问题,即线段树维护的东西通常有两种状态。

线段树通常用结构体来封装,如果不嫌眼花缭乱,可以用数组写。

结构体中有lsum,rsum,sum三个重要的变量,lsum是在当前区间从左端点开始向右最长的连续的为1的点的数量,那么rsum就从右端点开始了,sum是当前整个区间最长的1串。

最关键的就是pushup函数如何写。

void pushup(int cnt)
{
	tree[cnt].ls=tree[lson].sum==tree[lson].r-tree[lson].l+1?tree[lson].sum+tree[rson].ls:tree[lson].ls;
	tree[cnt].rs=tree[rson].sum==tree[rson].r-tree[rson].l+1?tree[rson].sum+tree[lson].rs:tree[rson].rs;
	tree[cnt].sum=max(tree[lson].rs+tree[rson].ls,max(tree[lson].sum,tree[rson].sum));
}

直接看着代码就比较容易理解了。

lsum就是从左子树中找,如果左子树全是1,就再加上右子树的lsum就是最长的了。rsum亦是如此。

sum就是左子树和右子树的sum,以及左子树的rsum+右子树的lsum中的最大值了。

知道这些pushup就迎刃而解了。剩下的就比较基础了。

#include<cstdio>
#include<stack>
#include<cstring>
#include<algorithm>
#define lson cnt<<1
#define rson cnt<<1|1
using namespace std;
//线段树01串区间合并 单点修改 查询
const int maxn=5e4+10;

struct node{
	int l,r;
	int ls,rs,sum;
}tree[maxn<<2];

void pushup(int cnt)
{
	tree[cnt].ls=tree[lson].sum==tree[lson].r-tree[lson].l+1?tree[lson].sum+tree[rson].ls:tree[lson].ls;
	tree[cnt].rs=tree[rson].sum==tree[rson].r-tree[rson].l+1?tree[rson].sum+tree[lson].rs:tree[rson].rs;
	tree[cnt].sum=max(tree[lson].rs+tree[rson].ls,max(tree[lson].sum,tree[rson].sum));
}
void build(int cnt,int l,int r)
{
    //printf("hello\n");
	tree[cnt].l=l;
	tree[cnt].r=r;
	tree[cnt].ls=tree[cnt].rs=tree[cnt].sum=r-l+1;
	if(l!=r)
	{
	    int mid=(tree[cnt].l+tree[cnt].r)>>1;
	    build(lson,l,mid);
        build(rson,mid+1,r);
	}
    return;
}
void update(int p,int k,int cnt)
{
	if(tree[cnt].l==tree[cnt].r)
	{
		if(k)
			tree[cnt].ls=tree[cnt].rs=tree[cnt].sum=1;
		else
			tree[cnt].ls=tree[cnt].rs=tree[cnt].sum=0;
        return;
	}
	int mid=(tree[cnt].l+tree[cnt].r)>>1;
	if(p<=mid)
		update(p,k,lson);
	else
		update(p,k,rson);
	pushup(cnt);
}
int query(int cnt,int p)
{
	if(tree[cnt].l==tree[cnt].r||tree[cnt].sum==0||tree[cnt].sum==tree[cnt].r-tree[cnt].l+1)
		return tree[cnt].sum;
    int mid=(tree[cnt].l+tree[cnt].r)>>1;
	if(p<=mid)
	{
		if(p>=tree[lson].r-tree[lson].rs+1)
			return query(lson,p)+query(rson,mid+1);
		else
			return query(lson,p);
	}
	else
	{
		if(p<=tree[rson].l+tree[rson].ls-1)
			return query(lson,mid)+query(rson,p);
		else
			return query(rson,p);
	}
}
stack<int>s;
int main()
{
	int n,m;
	while(~scanf("%d %d",&n,&m))
	{
		while(s.size())
			s.pop();

		build(1,1,n);

		char op[2];
		int x;
		while(m--)
		{
			scanf("%s",op);
			if(op[0]=='D')
			{
				scanf("%d",&x);
				update(x,0,1);
				s.push(x);
			}
			else if(op[0]=='Q')
			{
				scanf("%d",&x);
				printf("%d\n",query(1,x));
			}
			else if(op[0]=='R')
			{
				update(s.top(),1,1);
				s.pop();
			}
		}
	}
	return 0;
}

3、扫描线(抓稳了!!!)

这种问题目前我只会做求覆盖后矩形面积,蒟蒻一枚。。

HDU - 1542

There are several ancient Greek texts that contain descriptions of the fabled island Atlantis. Some of these texts even include maps of parts of the island. But unfortunately, these maps describe different regions of Atlantis. Your friend Bill has to know the total area for which maps exist. You (unwisely) volunteered to write a program that calculates this quantity. 

Input

The input file consists of several test cases. Each test case starts with a line containing a single integer n (1<=n<=100) of available maps. The n following lines describe one map each. Each of these lines contains four numbers x1;y1;x2;y2 (0<=x1<x2<=100000;0<=y1<y2<=100000), not necessarily integers. The values (x1; y1) and (x2;y2) are the coordinates of the top-left resp. bottom-right corner of the mapped area. 

The input file is terminated by a line containing a single 0. Don’t process it.

Output

For each test case, your program should output one section. The first line of each section must be “Test case #k”, where k is the number of the test case (starting with 1). The second one must be “Total explored area: a”, where a is the total explored area (i.e. the area of the union of all rectangles in this test case), printed exact to two digits to the right of the decimal point. 

Output a blank line after each test case. 

Sample Input

2
10 10 20 20
15 15 25 25.5
0

Sample Output

Test case #1
Total explored area: 180.00 

在二维平面内,给你n个矩形,每个矩形给出其左下角坐标及右上角坐标,求出整个图形面积。

利用扫描线把矩形分块。贴一个大佬博客地址

贴两份代码,不同的写法。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#define lson cnt<<1
#define rson cnt<<1|1
using namespace std;

const int maxn=100*2+10;

struct node1{
    double l,r;
    double h;
    int f;
}seg[maxn];
struct node2{
    int l,r;
    double val;
    int f;
}tree[maxn<<2];

double pre[maxn];
void build(int cnt,int l,int r)
{
    tree[cnt].l=l;
    tree[cnt].r=r;
    tree[cnt].val=0;
    tree[cnt].f=0;
    if(l==r)
        return;
    int mid=(l+r)>>1;
    build(lson,l,mid);
    build(rson,mid+1,r);
}
void pushup(int cnt)
{
    if(tree[cnt].f>0)//
        tree[cnt].val=pre[tree[cnt].r+1]-pre[tree[cnt].l];
    else if(tree[cnt].l==tree[cnt].r)
        tree[cnt].val=0;
    else
        tree[cnt].val=tree[lson].val+tree[rson].val;
}
void update(int L,int R,int cnt,int k)
{
    if(L<=tree[cnt].l&&tree[cnt].r<=R)
    {
        tree[cnt].f+=k;
        pushup(cnt);
        return;
    }
    int mid=(tree[cnt].l+tree[cnt].r)>>1;
    if(L<=mid)
        update(L,R,lson,k);
    if(R>mid)
        update(L,R,rson,k);
    pushup(cnt);
}
bool cmp(node1 a,node1 b)
{
    return a.h<b.h;
}
int main()
{
    int n;
    double x1,x2,y1,y2;
    int cas=1;
    while(~scanf("%d",&n)&&n)
    {
        vector<double>v;
        for(int i=1;i<=n;i++)
        {
            scanf("%lf %lf %lf %lf",&x1,&y1,&x2,&y2);
            seg[i*2-1].l=x1,seg[i*2-1].r=x2,seg[i*2-1].h=y1,seg[i*2-1].f=1;
            seg[i<<1].l=x1,seg[i<<1].r=x2,seg[i<<1].h=y2,seg[i<<1].f=-1;
            v.push_back(x1);
            v.push_back(x2);
        }
        //printf("hello\n");
        sort(v.begin(),v.end());
        v.erase(unique(v.begin(),v.end()),v.end());
        vector<double>::iterator it;
        int k=1;
        for(it=v.begin();it!=v.end();it++)
            pre[k++]=*it;//ÀëÉ¢»¯ºóµÄÇø¼äÊÇ[1,k-1]
        build(1,1,k-1);
        sort(seg+1,seg+2*n+1,cmp);
        double ans=0;
//        for(int i=1;i<=2*n;i++)
//        	printf("%lf %lf\n",seg[i].l,seg[i].r);
        //printf("world\n");
        for(int i=1;i<=n*2;i++)
        {
            int l0=lower_bound(pre+1,pre+k,seg[i].l)-pre;
            int r0=lower_bound(pre+1,pre+k,seg[i].r)-pre-1;
            update(l0,r0,1,seg[i].f);
            //printf("%lf %lf %lf\n",pre[l0],pre[r0],tree[1].val);
           ans+=(seg[i+1].h-seg[i].h)*tree[1].val;
        }
        printf("Test case #%d\n",cas++);
		printf("Total explored area: %.2f\n\n",ans);
    }
    return 0;
}
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define eps 1e-8
using namespace std;

const int maxn=100*2+10;

struct node1{
	double l,r,h;
	int flag;
}seg[maxn];

struct node2{
	double l,r;
	double val;
	int flag;
	int leaf;  
}tree[maxn*4];

double pre[maxn];
int n;

bool cmp(node1 a,node1 b)
{
	return a.h<b.h;
}
void build(int l,int r,int cnt)
{
	tree[cnt].l=pre[l];
	tree[cnt].r=pre[r];
	tree[cnt].val=0;
	tree[cnt].flag=0;
	tree[cnt].leaf=0;
	if(l+1==r)//???
	{
		tree[cnt].leaf=1;
		return;
	}
	int mid=(l+r)>>1;
	build(l,mid,cnt<<1);
	build(mid,r,cnt<<1|1);
}
void pushup(int cnt)
{
	if(tree[cnt].flag>0)
	{
		tree[cnt].val=tree[cnt].r-tree[cnt].l;
	}
	else
	{
		if(tree[cnt].leaf)
			tree[cnt].val=0;
		else
			tree[cnt].val=tree[cnt<<1].val+tree[cnt<<1|1].val;
	}
}
void update(int cnt,double L,double R,int k)
{
	if((L<tree[cnt].l||fabs(L-tree[cnt].l)<eps)&&(tree[cnt].r<R||fabs(tree[cnt].r-R)<eps))
	{
		tree[cnt].flag+=k;
		pushup(cnt);
		return;
	}
	if(L<tree[cnt<<1].r)
		update(cnt<<1,L,R,k);
	if(R>tree[cnt<<1|1].l)
		update(cnt<<1|1,L,R,k);
	pushup(cnt);
}
int main()
{
	double x1,x2,y1,y2;
	double ans;
	int cas=1;
	while(~scanf("%d",&n)&&n)
	{
		for(int i=1;i<=n;i++)
		{
			scanf("%lf %lf %lf %lf",&x1,&y1,&x2,&y2);
			seg[i*2-1].l=x1;seg[i*2-1].r=x2;seg[i*2-1].h=y1;seg[i*2-1].flag=1;
			seg[i*2].l=x1;seg[i*2].r=x2;seg[i*2].h=y2;seg[i*2].flag=-1;
			pre[i*2-1]=x1;pre[i*2]=x2;
		}
		sort(pre+1,pre+2*n+1);
		int j=0;
		for(int i=1;i<=n*2;i++)
		{
			if(j==0||fabs(pre[i]-pre[j])>eps)
			{
				pre[++j]=pre[i];
			}
		}
		build(1,j,1);
		sort(seg+1,seg+2*n+1,cmp);
		ans=0;
		for(int i=1;i<=2*n;i++)
		{
			ans+=(seg[i].h-seg[i-1].h)*tree[1].val;
			update(1,seg[i].l,seg[i].r,seg[i].flag);
		}
		printf("Test case #%d\n",cas++);
		printf("Total explored area: %.2f\n\n",ans);
	}
	return 0;
}

zxf和ly回宿舍了,不写了

发布了11 篇原创文章 · 获赞 8 · 访问量 737

猜你喜欢

转载自blog.csdn.net/qq_41765862/article/details/81611126
今日推荐