引入
题 目 描 述 题目描述 题目描述
10000 10000 10000个正整数,编号从 1 1 1到 10000 10000 10000,用 A [ 1 ] A[1] A[1], A [ 2 ] A[2] A[2], A [ 10000 ] A[10000] A[10000]表示。
修改:1.将第 p o s pos pos 个数增加 v a l val val ( 1 < = L < = 10000 ) (1 <= L <= 10000) (1<=L<=10000)
统计:1.编号从 L L L 到 R R R 的所有数之和为多少? 其中 1 < = L < = R < = 10000. 1<= L <= R <= 10000. 1<=L<=R<=10000.
方法如下(目前所学):
- 修改 A [ p o s ] A[pos] A[pos] —— O ( 1 ) O(1) O(1) ;循环求和 —— O ( n ) O(n) O(n)
- 差分:修改 n n n 个元素 O ( n ) O(n) O(n) ; 令 S[0]=0, S[k]=A[1…k] ,那么,A[L…R]的和就等于S[R]-S[L-1] ——— O ( 1 ) O(1) O(1)
从上可以看出 方法一修改快,求和慢。 方法二求和快,修改慢。
那有没有一种结构,修改和求和都比较快呢?答案当然是
线
段
树
线段树
线段树
线段树的性质
众所周知, 线段树是一棵完全二叉树
设其中一子节点编号为
a
a
a , 则该节点左儿子编号为
2
a
2a
2a , 其右儿子编号为
2
a
+
1
2a+1
2a+1
另:线段树的子节点对应一个区间 , 顾名思义
线段树的算法
- 结构体(tree)
struct Tree
{
int left,right,val=INT_MAX;
}tree[maxn<<2];//定义
inline void push_up(int )
{
tree[o].val=min(tree[left].val,tree[right].val);//根据题目要求
}//向上调整
void build(int k,int l,int r)
{
tree[k].left=l;
tree[k].right=r;
if(l==r)
{
tree[k].val=read();//tree[k].val=a[l];
return;
}
int mid=(l+r)/2;
int left=k*2,right=k*2+1;
build(left,l,mid);
build(right,mid+1,r);
push_up();
}//建树
- 多个数组
函数需多加两个引用
int tree[maxn<<2];//定义
inline void build(int o, int l, int r)
{
if (l == r)
{
cin >> tree[o];
return;
}
int mid = (l + r) >> 1;
build(lw(o), l, mid);
build(rw(o), mid + 1, r);
push_up(o);
}//建图
inline void update(int o, int l, int r, int pos, int val);//单点更新
inline int query(int o, int left, int right, int l, int r)//区间查询
//left和right是目标区间
//l和r是节点区间(开始一般取1,n)
线段树的单点修改
1、分解区间
首先是讲原始子区间的分解,假定给定区间 [ L , R ] [L,R] [L,R],只要 L < R L < R L<R ,线段树就会把它继续分裂成两个区间。
首先计算 M = ( L + R ) / 2 M = (L+R)/2 M=(L+R)/2,左子区间为 [ L , M ] [L,M] [L,M],右子区间为 [ M + 1 , R ] [M+1,R] [M+1,R],然后如果子区间不满足条件就继续递归分解。
分解条件:要赋值的位置在 m i d mid mid左则进入区间 [ L , M I D ] [L,MID] [L,MID],否则进入区间 [ M I D + 1 , R ] [MID+1,R] [MID+1,R]
2、判断子区间是否左右相等
- 若相等,则赋值,return
- 若不相等,则继续 分 解 区 间 分解区间 分解区间(参考1),并向上调整(push_up)
线段树的区间查询
1、继续分解区间
2、判断当前区间是否位于目标区间之内(完全包含)
- 若包含,则返回ans
- 若不包含,则继续 分 解 区 间 分解区间 分解区间,并向上调整
int sum1=INT_MAX,sum2=INT_MAX;
if(L<=mid)
{
sum1=query(left,L,R);
}
if(R>mid)
{
sum2=query(right,L,R);
}
最后sum1和sum2取最小值返回
模板
#include<bits/stdc++.h>
using namespace std;
int read()
{
int s=0; char c=getchar();
for (;!isdigit(c);c=getchar());
for (;isdigit(c);c=getchar()) s=s*10+c-'0';
return s;
}
const int maxn=1e5;
int n,m;
struct Tree
{
int left,right,val=INT_MAX;
}tree[maxn*4+99];
void build(int k,int l,int r)
{
tree[k].left=l;
tree[k].right=r;
if(l==r)
{
tree[k].val=read();
return;
}
int mid=(l+r)/2;
int left=k*2,right=k*2+1;
build(left,l,mid);
build(right,mid+1,r);
tree[k].val=min(tree[left].val,tree[right].val);
}
void update(int o,int pos,int val)
{
if(tree[o].left==tree[o].right)
{
tree[o].val=val;
return;
}
int mid=(tree[o].left+tree[o].right)/2;
int left=o*2,right=o*2+1;
if(pos<=mid)
{
update(left,pos,val);
}
else
{
update(right,pos,val);
}
tree[o].val=min(tree[left].val,tree[right].val);
}
int query(int o,int L,int R)
{
if(L<=tree[o].left&&R>=tree[o].right)
{
return tree[o].val;
}
int mid=(tree[o].left+tree[o].right)/2;
int left=o*2,right=o*2+1;
int sum1=INT_MAX,sum2=INT_MAX;
if(L<=mid)
{
sum1=query(left,L,R);
}
if(R>mid)
{
sum2=query(right,L,R);
}
return min(sum1,sum2);
}
signed main()
{
cin>>n>>m;
build(1,1,n);
//for(int i=1;i<=n;i++)cout<<summ[i]<<" ";
//cout<<endl;
for(int i=1;i<=m;i++)
{
int q,l,r;
q=read();
l=read();
r=read();
if(q==2)
{
update(1,l,r);
}
else
{
cout<<query(1,l,r)<<" ";
}
}
}
例题
题目描述
猫猫 TOM 和小老鼠 JERRY 最近又较量上了,但是毕竟都是成年人,他们已经不喜欢再玩那种你追我赶的游戏,现在他们喜欢玩统计。
最近,TOM 老猫查阅到一个人类称之为“逆序对”的东西,这东西是这样定义的:对于给定的一段正整数序列,逆序对就是序列中 a [ i ] > a [ j ] 且 i < j a[i]>a[j] 且 i<j a[i]>a[j]且i<j
且 i < j i<j i<j 的有序对。知道这概念后,他们就比赛谁先算出给定的一段正整数序列中逆序对的数目。注意序列中可能有重复数字。
Update:数据已加强。
输入格式
第一行,一个数 n n n,表示序列中有 n n n 个数。
第二行 n n n 个数,表示给定的序列。序列中每个数字不超过 1 0 9 10^9 109 。
输出格式
输出序列中逆序对的数目。
输入 #1
6
5 4 2 6 3 1
输出 #1
11
思路
本题采用 值 域 线 段 树 值域线段树 值域线段树
原
tree[o]=val;
现
tree[o]++;
意为统计某个数 o o o出现的个数
- 先对数据进行
离
散
化
离散化
离散化(
l
o
w
e
r
b
o
u
n
d
lower_bound
lowerbound)
e g . eg. eg.
1 2 3 4 9 7
1 2 3 4 6 5 - 再边记录边查询(查询比数 i i i 大的数 j j j 出现的个数)
- ans累加
本人AC程序
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define lw(a) a << 1
#define rw(a) (a << 1) | 1
const int maxn = 2e5 + 5;
int n, a[maxn], b[maxn];
int tree[maxn << 2];
int ans;
inline int read()
{
char c = getchar();
int x = 0;
bool f = 0;
for (; !isdigit(c); c = getchar())
{
f ^= !(c ^ 45);
}
for (; isdigit(c); c = getchar())
{
x = (x << 1) + (x << 3) + (c ^ 48);
}
if (f)
{
x = -x;
}
return x;
}
inline void write(int x)
{
if (x < 0)
{
putchar('-');
x = -x;
}
if (x > 9)
{
write(x / 10);
}
putchar(x % 10 + '0');
}
inline bool comp(int x, int y)
{
return b[x] > b[y];
}
inline void push_up(int x)
{
tree[x] = tree[lw(x)] + tree[rw(x)];
}
inline void update(int o, int l, int r, int pos)
{
if (l == r)
{
tree[o]++;
return;
}
int mid = (l + r) >> 1;
if (pos <= mid)
{
update(lw(o), l, mid, pos);
}
else
{
update(rw(o), mid + 1, r, pos);
}
push_up(o);
}
inline int query(int o, int left, int right, int l, int r)
{
if (left <= l && r <= right)
{
return tree[o];
}
int mid = (l + r) >> 1;
int ans = 0;
if (left <= mid)
{
ans += query(lw(o), left, right, l, mid);
}
if (right > mid)
{
ans += query(rw(o), left, right, mid + 1, r);
}
return ans;
}
signed main()
{
n = read();
for (int i = 1; i <= n; i++)
{
b[i] = read();
a[i] = b[i];
}
sort(b + 1, b + n + 1);
for (int i = 1; i <= n; ++i)
{
a[i] = lower_bound(b + 1, b + n + 1, a[i]) - b;
}
//for (int i = 1; i <= n; i++)
//cout << a[i] << " ";
//cout << endl;
for (int i = 1; i <= n; i++)
{
update(1, 1, n, a[i]);
ans += query(1, a[i] + 1, n, 1, n);
}
write(ans);
return 0;
}