一、线段树
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;
}
参考文档: