线段树和树状数组

一、线段树

1、动态查询第k大(知道每个位置前面有多少个比自己小的数,求这个数列的顺序)

题目描述: 有编号1~n的n个数字,2 <= n <= 8000, 乱序排列,顺序是未知的。对于每个位置的数字,知道排在前面比它小的数字有多少个,求这个乱序数列的顺序。

POJ 2182 Lost Cows (简单题,线段树)_謙卑的博客-CSDN博客

#include<iostream>
#include<vector>
using namespace std;

const int N = 8005;
struct node {
	int l;
	int r;
	int num;
}tr[4*N];

//建树
void build(int pos, int l, int r) {    
	tr[pos].l = l;
	tr[pos].r = r;
	tr[pos].num = r - l + 1;
	if (l == r)	return;
	int mid = (l + r) / 2;
	build(2 * pos, l, mid);
	build(2 * pos + 1, mid + 1, r);
}

//动态查询第k大
int query(int pos, int n) {						
	tr[pos].num--;
	if (tr[pos].l == tr[pos].r)	return tr[pos].l;
	if (tr[2 * pos].num < n)	return query(2 * pos + 1, n - tr[2 * pos].num);
	else     return query(2 * pos, n);
}

int main() {
	int n;
	cin >> n;
	vector<int>pre(n + 1, 0);					//记录前面有多少个比自己小的
	vector<int>ans(n + 1);
	for (int i = 2; i <= n; i++)
		cin >> pre[i];
	build(1, 1, n);
	for (int i = n; i >= 1; i--)	ans[i] = query(1, pre[i] + 1);
	for (int i = 1; i <= n; i++)			cout << ans[i] <<endl;
	return 0;
}

2、动态查询连续区间和 

线段树 从入门到进阶(超清晰,简单易懂)_线段树进阶_繁凡さん的博客-CSDN博客

线段树的应用     敌兵布阵

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<iostream>
#include<vector>

using namespace std;

const int maxn = 50005;
int a[maxn];

struct node {
	int l;
	int r;
	int sum;
}tr[4*maxn];


//建树
void build(int pos, int l, int r) {
	if (l == r) {
		tr[pos].l = l;
		tr[pos].r = r;
		tr[pos].sum = a[l];
		return;
	}
	int mid = (l + r) / 2;
	build(2 * pos, l, mid);
	build(2 * pos + 1, mid + 1, r);
	tr[pos].l = l;
	tr[pos].r = r;
	tr[pos].sum = tr[2 * pos].sum + tr[2 * pos + 1].sum;	
}

//修改
void modify(int pos, int n, int val) {
	tr[pos].sum += val;
	if (tr[pos].l == tr[pos].r)	return;
	int mid = (tr[pos].l + tr[pos].r) / 2;
	if (n > mid)	modify(2 * pos + 1, n, val);
	else   modify(2 * pos, n, val);
}

//查询
int Query(int pos, int l, int r) {
	if (tr[pos].l == l && tr[pos].r == r)	return tr[pos].sum;
	int mid = (tr[pos].l + tr[pos].r) / 2;
	if (r <= mid)	return Query(2 * pos, l, r);
	else if (l > mid)	return Query(2 * pos + 1, l, r);
	else return Query(2 * pos, l, mid) + Query(2 * pos + 1, mid + 1, r);
}

int main() {
	int T;
	cin >> T;
	while (T) {
		int n;
		cin >> n;
		for (int i = 1; i <= n; i++)
			cin >> a[i];
		build(1, 1, n);
		string order;
		cin >> order;
		while (order.compare("End") != 0) {
			int x, y;
			cin >> x >> y;
			if (order.compare("Add") == 0) {
				modify(1, x, y);
			}
			else if (order.compare("Sub") == 0) {
				modify(1, x, -1 * y);
			}
			else if (order.compare("Query") == 0) {
				cout << Query(1, x, y);
			}
			cin >> order;
		}
		T--;
	}
	return 0;
}

二、树状数组

五分钟丝滑动画讲解 | 树状数组_哔哩哔哩_bilibili

树状数组 数据结构详解与模板(可能是最详细的了)_bestsort的博客-CSDN博客

1、单点修改和区间求和查询

重要知识点:

        ①有用的数据正好有n个

         ②修改某个数据时只需向上找到包含它的区间进行修改即可

         ③b[i]序列长度==lowBit(i)

         ④b[i]正上方的序列,正好是b[i+lowBit(i)]

 

 

树状数组写法:

int lowBit(int x) {
	return x & (-x);
}

//更新第x个序列及它正上方的所有序列
void update(int x, int val) {									
	for (int i = x; i <= n; i += lowBit(i))
		b[i] += val;
}

//查询前x个营地的人数和
int Query(int x) {
	int sum = 0;
	for (int i = x; i; i -= lowBit(i))
		sum += b[i];
	return sum;
}

 

 

奶牛 (树状数组)

农夫约翰的牛发现,他的田地里沿着山脊生长的三叶草(我们可以将其视为一维数字线)特别好。

农夫约翰有N头母牛(我们将母牛的编号从1到N)。每位农夫约翰的N头母牛都有她特别喜欢的三叶草范围(这些范围可能重叠)。范围由闭合间隔[S,E]定义。

但是有些母牛很强壮,有些却很弱。给定两个母牛:母牛i和母牛j,它们最喜欢的三叶草范围是[Si,Ei]和[Sj,Ej]。如果Si <= Sj并且Ej <= Ei并且Ei-Si> Ej-Sj,我们说母牛i比母牛j强。

对于每头母牛,有几头母牛比她强?农夫约翰需要您的帮助!

输入项

输入包含多个测试用例。

对于每个测试用例,第一行是整数N(1 <= N <= 10 ^ 5),它是母牛的数量。然后是N行,其第i行包含两个整数:S和E(0 <= S<=E<=1e5 )

输入的末尾包含单个0。

输出量

对于每个测试用例,输出一行包含n个以空格分隔的整数,其中第i个数字指定比母牛i强的母牛的数量。

Sample Input

3

1 2

0 3

3 4

0

Sample Output

1 0 0

 

 思路:

先将牛按e边界排序,e大的排在前面,如果两个e相等,s小的排前面;

因为比自己强壮的牛它的e边界一定是大于等于自己的e的,所以比它强壮的牛一定排在它前面,我们只需统计前面有几个s边界小于等于自己s的,这个个数就是比自己强壮的牛的个数。(利用树状数组来统计小于等于自己s的有多少个)

#include <iostream>;
#include<vector>;
#include<algorithm>
using namespace std; 

const int maxn = 100001;
struct cow{
    int s;
    int e;
    int id;
}a[maxn];                        //存储牛的信息    
int tr[maxn];                    //树状数组
int p[maxn];                    //存储结果

bool cmp(cow& x, cow& y) {
    if (x.e == y.e)
        return x.s < y.s;
    return x.e > y.e;
}

int lowBit(int x) {
    return x & (-x);
}

void add(int x, int k) {
    for (int i = x; i<maxn; i += lowBit(i)) {
        tr[i] += k;
    }
}

int getsum(int x) {                                        //统计s边界小于等于当前s的个数
    int sum = 0;
    for (int i = x; i > 0; i -= lowBit(x))
        sum += tr[i];
    return sum;
}

int main() {
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i].s >> a[i].e;
        a[i].s++;
        a[i].e++;
        a[i].id = i;
    }
    sort(a + 1, a + n + 1, cmp);
    for (int i = 1; i <= n; i++) {
        if (i > 1 && a[i].s == a[i - 1].s && a[i].e == a[i].e) {
            p[a[i].id] = p[a[i - 1].id];
            
        }
        else {
            p[a[i].id] = getsum(a[i].s);
        }
        add(a[i].s, 1);
    }
    for (int i = 1; i <= n; i++)
        cout << p[i] << " ";
    return 0;
}
 

 

参考文档:

https://www.cnblogs.com/lipu123/p/14040016.html 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值