2021-1-25 线段树

A——例题1(求最大子段和)

You are given a sequence A[1], A[2], …, A[N] . ( |A[i]| ≤ 15007 , 1 ≤ N ≤ 50000 ). A query is defined as follows:
Query(x,y) = Max { a[i]+a[i+1]+…+a[j] ; x ≤ i ≤ j ≤ y }.
Given M queries, your program must output the results of these queries.

Input

The first line of the input file contains the integer N.
In the second line, N numbers follow.
The third line contains the integer M.
M lines follow, where line i contains 2 numbers xi and yi.

output

Your program should output the results of the M queries, one query per line.

Sample Input

3
-1 2 3
1
1 2

Sample Output

2

大致题意:
给出n个数字,要求求出(x,y)范围内最大连续子段和。

题解:
一开始以为求区间最大值。。wa了好几次才发现不对劲,然后重写。
简单来说,就是多开几个数组,因为要连续子段,所以在找叶节点的最大连续子段时要考虑左子段和右子段的连续性。

#include<iostream>
using namespace std;
int arr[100005];

typedef struct Tree{
    
    
    int left,right;//表示区间的范围 
    int ssum;//这个区间的最大连续和 
    int sum_;//区间和 
    int suml;//储存从左边开始的最大连续和 
    int sumr;//储存从右边开始的最大连续和 
}; 
Tree tree[500005*4];
int num=0;

void build(int node,int left,int right){
    
    
    tree[node].left=left;
    tree[node].right=right;//储存区间的范围 
    if(left==right){
    
    
        num=node;
        tree[node].ssum=arr[left];
        tree[node].suml=arr[left];
        tree[node].sum_=arr[left];
        tree[node].sumr=arr[left];
    }
    else{
    
    
        int mid=(left+right)/2;
        build(node*2,left,mid);
        build(node*2+1,mid+1,right);
        tree[node].sum_=tree[node*2].sum_+tree[node*2+1].sum_;
        //sum_代表区间和,把左区间的和 和 右区间的和相加 
        tree[node].ssum=max(max(tree[node*2].ssum,tree[node*2+1].ssum),(tree[node*2].sumr+tree[node*2+1].suml));
        //一个区间的左右区间的最大连续和,要么是左区间的最大连续和,要么是右区间的最大连续和,要么是左区间的最大右连续和加上右区间的最大左连续和
        tree[node].suml=max(tree[node*2].suml,tree[2*node].sum_+tree[2*node+1].suml);
        //该区间从左边起的最大连续和,要么是左子区间的最大左连续和,要么是左子区间的和+右子区间的最大左连续和
        tree[node].sumr=max(tree[node*2+1].sumr,tree[2*node].sumr+tree[2*node+1].sum_);
        //最大右连续和同理 
    }
}


Tree query(int node,int left,int right){
    
    
    if(tree[node].left==left&&tree[node].right==right)//如果查询区间和节点区间重合,只需返回节点的ssum即可 
        return tree[node];
    else{
    
    
        int mid=(tree[node].left+tree[node].right)/2;
        if(right<=mid)
            return query(2*node,left,right);//如果查询区间在左子区间内,就查询左子区间 
        if(left>mid)
            return query(2*node+1,left,right);//如果查询区间在右子区间内,就接下去查询右子区间 
        //查询区间被包含在该区间(left,right) 内 
        Tree kl,kr,ans;    
        kl=query(2*node,left,mid);
        kr=query(2*node+1,mid+1,right);

        ans.sum_=kl.sum_+kr.sum_;
        ans.ssum=max(max(kl.ssum,kr.ssum),kl.sumr+kr.suml);
        ans.suml=max(kl.suml,kl.sum_+kr.suml);
        ans.sumr=max(kr.sumr,kl.sumr+kr.sum_);
        return ans;
    }
}

int main(){
    
    
    int n,m;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&arr[i]);
    build(1,1,n);
    scanf("%d",&m);
    while(m--){
    
    
        int l,r;
        scanf("%d%d",&l,&r);
        Tree temp=query(1,l,r);
        printf("%d\n",temp.ssum);
    }
    return 0;
}

G——练习题2

At the entrance to the university, there is a huge rectangular billboard of size h*w (h is its height and w is its width). The board is the place where all possible announcements are posted: nearest programming competitions, changes in the dining room menu, and other important information.

On September 1, the billboard was empty. One by one, the announcements started being put on the billboard.

Each announcement is a stripe of paper of unit height. More specifically, the i-th announcement is a rectangle of size 1 * wi.

When someone puts a new announcement on the billboard, she would always choose the topmost possible position for the announcement. Among all possible topmost positions she would always choose the leftmost one.

If there is no valid location for a new announcement, it is not put on the billboard (that’s why some programming contests have no participants from this university).

Given the sizes of the billboard and the announcements, your task is to find the numbers of rows in which the announcements are placed.

Input

There are multiple cases (no more than 40 cases).

The first line of the input file contains three integer numbers, h, w, and n (1 <= h,w <= 10^9; 1 <= n <= 200,000) - the dimensions of the billboard and the number of announcements.

Each of the next n lines contains an integer number wi (1 <= wi <= 10^9) - the width of i-th announcement.

Output

For each announcement (in the order they are given in the input file) output one number - the number of the row in which this announcement is placed. Rows are numbered from 1 to h, starting with the top row. If an announcement can’t be put on the billboard, output “-1” for this announcement.

Sample Input

3 5 5
*2
4
3
3
3

Sample Output

1
2
1
3
-1
大致题意:
有n张广告(长为wi,高为1)要贴到广告牌(长为w,高为h)上去,尽可能往左、往上贴。
题解:
如果直接给我这道题,我肯定是想不到线段树的,可能会用数组做吧。。但是它在线段数这个专题里,所以看完题目我就想了一下怎么用线段树做。看了半天,也只有广告牌的高度可以用来作线段树了。但是,为了减少建树的范围,因为只有n张广告,所以最坏情况就是每张广告贴一行,树的最大范围是n。然后只需将节点数组中储存子节点的最大剩余长度,就可以递归判断究竟该储存在哪个子节点里了。

#include<iostream>
#include<cstdio>
#include<algorithm>
int h,w,n;
const int maxn=200000+10;

int max(int a,int b){
    
    
	if(a>b)return a;
	return b;
}

struct tree{
    
    
	int l,r;//储存行数的范围 
	int max;//储存最大长度 
}Tree[4*maxn];

void build(int node,int l,int r){
    
    
	Tree[node].l=l;
	Tree[node].r=r;
	if(l==r){
    
    Tree[node].max=w;return;}//处于同一行时 
	int mid=(l+r)/2;
	build(2*node,l,mid);
	build(2*node+1,mid+1,r);
	Tree[node].max=max(Tree[2*node].max,Tree[2*node+1].max);
}

int query(int node,int wi){
    
    
	if(Tree[node].l==Tree[node].r){
    
    //找到了放置该广告牌的行数 
		Tree[node].max-=wi;//将该广告牌放进该行 
		return Tree[node].l;
	}
	int ans;
	if(Tree[2*node].max>=wi){
    
    //左子树能存放下 ,存放在左子树 
		ans=query(2*node,wi);
	}
	else{
    
    
		ans=query(2*node+1,wi);//左子树放不下了就放进右子树 
	}
	Tree[node].max=max(Tree[2*node].max,Tree[2*node+1].max);//更新节点能储存的最大长度
	return ans; 
}

int main(){
    
    
	while(~scanf("%d %d %d",&h,&w,&n)){
    
    
		if(h>n)h=n;//空间优化,因为只有n块广告牌,所以最多放置n行 
		build(1,1,h);
		while(n--){
    
    
			int wi;
			scanf("%d",&wi);
			int ans=-1;
			if(wi<=Tree[1].max)ans=query(1,wi);//只有存在能放下的行时,才会去寻找
			printf("%d\n",ans); 
		}
	}
	return 0;
}

线段树的模板

1.建树模板

inline void build(int i,int l,int r){
    
    //递归建树
    tree[i].l=l;tree[i].r=r;
    if(l==r){
    
    //如果这个节点是叶子节点
        tree[i].sum=input[l];
        return ;
    }
    int mid=(l+r)>>1;
    build(i*2,l,mid);//分别构造左子树和右子树
    build(i*2+1,mid+1,r);
    tree[i].sum=tree[i*2].sum+tree[i*2+1].sum;//刚才我们发现的性质return ;
}

2.求和模板

inline int search(int i,int l,int r){
    
    
    if(tree[i].l>=l && tree[i].r<=r)//如果这个区间被完全包括在目标区间里面,直接返回这个区间的值
        return tree[i].sum;
    if(tree[i].r<l || tree[i].l>r)  return 0;//如果这个区间和目标区间毫不相干,返回0
    int s=0;
    if(tree[i*2].r>=l)  s+=search(i*2,l,r);//如果这个区间的左儿子和目标区间又交集,那么搜索左儿子
    if(tree[i*2+1].l<=r)  s+=search(i*2+1,l,r);//如果这个区间的右儿子和目标区间又交集,那么搜索右儿子
    return s;
}

3.单点修改模板

inline void add(int i,int dis,int k){
    
    
    if(tree[i].l==tree[i].r){
    
    //如果是叶子节点,那么说明找到了
        tree[i].sum+=k;
        return ;
    }
    if(dis<=tree[i*2].r)  add(i*2,dis,k);//在哪往哪跑
    else  add(i*2+1,dis,k);
    tree[i].sum=tree[i*2].sum+tree[i*2+1].sum;//返回更新
    return ;
}

猜你喜欢

转载自blog.csdn.net/m0_52433146/article/details/113146641
今日推荐