英文名称:Segment Tree
相比于树状数组,是一种更加通用的结构。
线段树的规律研究:
除去最后一层,发现线段树是一个完全二叉树。
所以可以像二叉堆那样,使用一个数组来存储整棵二叉树
线段树的高度是 l o g N logN logN
对于具有N个节点的满二叉树,需要有 2 N − 1 2N-1 2N−1个节点。但是对线段树,最后一层也许会有枝叶,所以应该创建 4 N 4N 4N的数组。
下面使用维护一个区间最大值的这一个任务来熟悉线段树的基本使用
父亲节点对应区间的最大值等于两个儿子节点的最大值的最大值
线段树的建立方法:
#include
using namespace std;
#define SIZE 100
struct SegmentTree{
int l, r;
int data;
}t[4*SIZE];
int s[SIZE];//表示需要被维护最大值的区间
void build(int p, int l, int r)
{
t[p].l = l;
t[p].r = r;
if(l == r)
{
t[p].data = s[l];
return;
}
int mid = (l+r)/2;
build(p*2, l, mid);
build(p*2+1, mid+1, r);
t[p].data = max(t[p*2].data, t[p*2+1].data);
}
int main()
{
int n;
cin>>n;
for(int i = 1; i <= n; i++)
scanf("%d", s+i);
build(1, 1, n);
return 0;
}
void change(int p, int x, int v)
//p表示当前的位置指针,x表示在s数组中需要修改的下标。v表示修改之后的值
{
if(t[p].l == t[p].r) {
t[p].data = v;
return;
}
int mid = (t[p].l+t[p].r) / 2;
if(x <= mid) change(2*p, x, v);
else change(2*p+1, x, v);
t[p].data = max(t[p*2].data, t[p*2+1].data);
}
有两种情况:
int ask(int p, int l, int r)
{
if(l <= t[p].l && r >= t[p].r)
{
return t[p].data;
}
int mid = (t[p].l + t[p].r)/2;
int ans = INT_MIN;
if(l <= mid) ans = max(ask(p*2, l, r), ans);
if(r > mid) ans = max(ask(p*2+1, l, r), ans);
return ans;
}
有以下几种情况:(不妨认为l,r表示要查询的区间)
( p l p_l pl表示节点的左端点, p r p_r pr表示节点的右端点)
不妨设 m i d = ⌊ p l + p r 2 ⌋ mid=\lfloor\frac{p_l+p_r}{2} \rfloor mid=⌊2pl+pr⌋
当 l ≤ p l 并 且 p r ≤ r l\leq p_l 并且 p_r\leq r l≤pl并且pr≤r,此时,不产生递归。直接返回
l > p l 并 且 l ≤ p r ≤ r l> p_l 并且 l\leq p_r\leq r l>pl并且l≤pr≤r
r < p r 并 且 l ≤ p l ≤ r r
线段树可以更加方便地维护各种区间的信息。
但是要注意:这些信息必须具备空间可加性!!
我的思路是把每一个区间的最大区间维护出来,在把该最大区间的左右端点维护出来,但是这样在进行传递的时候仅仅可以知道是否能把区间合并到一起,但是最大值又会发生变化,为此进行分析:
区间最大值的可能情况有以下三种情况
所以在维护最大值的同时,应该加上从区间的左边往右数的区间最大值lmax
以及右边往左数的区间最大值rmax
对于区间最大值,那么就是两个字树区间单独区间最大值与左子树的rmax
以及右子树的lmax
之中的最大值
即max(t[p*2].maxx, t[p*2+1].maxx, t[p*2].rmax+t[p*2+1].lmax)
但是对于lmax
以及rmax
有两种情况:
lmax
仍然是左子树的lmax
lmax
是左子树的全部区间加上右子树的lmax
sum
lmax,rmax,maxx,sum,l,r
#include
using namespace std;
#define N 20
//500000
int s[N];
class SegmentTree{
private:
struct T{
int l, r, lmax, rmax, maxx, sum;
}t[4*N];
public:
void build(int p, int l, int r)
{
t[p].l = l;
t[p].r = r;
if(l==r)
{
t[p].lmax = t[p].rmax = t[p].maxx = t[p].sum = s[l];
return;
}
int mid = (l+r)/2;
build(p*2, l, mid);
build(p*2+1, mid+1, r);
t[p].lmax = max(t[p*2].lmax, t[p*2].sum+t[p*2+1].lmax);
t[p].rmax = max(t[p*2+1].rmax, t[p*2+1].sum + t[p*2].rmax);
t[p].sum = t[p*2].sum + t[p*2+1].sum;
t[p].maxx = max(max(t[p*2].maxx, t[p*2+1].maxx), t[p*2].rmax + t[p*2+1].lmax);
return;
}
void change(int p, int x, int v)
{
if(t[p].l == t[p].r)
{
t[p].lmax = t[p].rmax = t[p].maxx = t[p].sum = v;
return ;
}
int mid = (t[p].l+t[p].r)/2;
if(x <= mid) change(p*2, x, v);
else change(p*2+1, x, v);
t[p].lmax = max(t[p*2].lmax, t[p*2].sum+t[p*2+1].lmax);
t[p].rmax = max(t[p*2+1].rmax, t[p*2+1].sum + t[p*2].rmax);
t[p].sum = t[p*2].sum + t[p*2+1].sum;
t[p].maxx = max(max(t[p*2].maxx, t[p*2+1].maxx), t[p*2].rmax + t[p*2+1].lmax);
return;
}
T ask_pro(int p, int l, int r)
{
if(l <= t[p].l && t[p].r <= r) return t[p];
T a, b, ans;
a.lmax = a.rmax = a.maxx = a.sum = b.sum = b.lmax = b.rmax = b.maxx = -0x3f3f3f3f;//注意,虽然INT_MIN更加小,但是可能会发生溢出!!!
ans.sum = 0;//ans其他的属性在之后会有更新,所以不需要初始化~~
int mid = (t[p].l + t[p].r)/2;
if(l <= mid)
{
a = ask_pro(2*p, l, r);
ans.sum += a.sum;
}
if(r > mid)
{
b = ask_pro(2*p+1, l, r);
ans.sum += b.sum;
}
ans.lmax = max(a.lmax, a.sum+b.lmax);//假设是有两个区间,在下面进行讨论
ans.rmax = max(b.rmax, a.rmax+b.sum);
ans.maxx = max(max(a.maxx, b.maxx), a.rmax+b.lmax);
if(l > mid) ans.lmax = max(ans.lmax, b.lmax);
if(r <= mid) ans.rmax = max(ans.rmax, a.rmax);
return ans;
}
int ask(int p, int l, int r)
{
return ask_pro(p, l, r).maxx;
}
};
SegmentTree t;
int main()
{
int n, m;
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++) scanf("%d", s+i);
t.build(1, 1, n);
for(int i = 1; i <= m; i++)
{
int k, x, y;
scanf("%d%d%d", &k, &x, &y);
if(k==1)//表示查询
{
if(x > y) swap(x, y);
printf("%d\n", t.ask(1, x, y));
}
else//表示修改
{
t.change(1, x, y);
}
}
return 0;
}
int ask(int p, int l, int r)
{
if(l <= t[p].l && t[p].r <= r)
{
return t[p].maxx;
}
int mid = (t[p].r + t[p].l)/2;
int ans = INT_MIN;
if(l <= mid) ans = max(ask(p*2, l, r), ans);
if(r > mid) ans = max(ask(p*2+1, l, r), ans);
return ans;
}
经过分析,发现是在ask这里发生了错误。
区间最大值的结果并不是一味地累加,而是要做许多决策。
注意:我在新改进的代码中,在ask中建立的ans,a,b,的l和r变量其实都没有用!!
有用的只有lmax和rmax和maxx
注意:线段树同样也是单点修改,区间查询
如果同时修改一个区间的值,求出一个差分序列或许会比较好。
注意:补充欧几里得定律
g c d ( a , b , c ) = g c d ( a , b − a , c − b ) gcd(a, b, c) = gcd(a, b-a, c-b) gcd(a,b,c)=gcd(a,b−a,c−b) [对于任意多个整数均成立]
这启示我们可以采用求差分来求gcd
设a[]
存储原始数据,b[]
存储a的差分
这个时候,gcd = gcd(a[l], b[l~r])
这样,还需要一个区间修改,单点查询的树状数组来查询a[l]
#include
using namespace std;
typedef long long ll;
#define N 500010
ll a[N];
ll gcd(ll x, ll y)
{
if(y==0) return x;
return gcd(y, x%y);
}
class BIT{
private:
ll c[N];
public:
void add(ll x, ll y, ll max)
{
for(; x <= max; x += x&-x)
{
c[x] += y;
}
}
ll ask(ll x)
{
ll ans = 0;
for(; x; x -= x&-x)
{
ans += c[x];
}
return ans;
}
};
class SegmentTree{
private:
struct {
ll ans, l, r;
}t[4*N];
public:
void build(ll p, ll l, ll r)
{
t[p].l = l;
t[p].r = r;
if(l == r)
{
t[p].ans = a[l];
return;
}
ll mid = (l + r)/2;
build(p<<1, l, mid);
build(p<<1|1, mid+1, r);
t[p].ans = gcd(t[p<<1].ans, t[p<<1|1].ans);
}
ll ask(ll p, ll l, ll r)
{
if(l <= t[p].l && t[p].r <= r) return t[p].ans;
ll mid = (t[p].l + t[p].r)>>1;
ll ans = 0;
if(l <= mid) ans = gcd(ask(p<<1, l, r), ans);
if(r > mid) ans = gcd(ans, ask(p<<1|1, l, r));// ******
return abs(ans);// ************
}
void add(ll p, ll x, ll y)
{
if(t[p].l == t[p].r)
{
t[p].ans += y;
return;
}
ll mid = (t[p].l+t[p].r)>>1;
if(x <= mid) add(p<<1, x, y);
else add(p<<1|1, x, y);
t[p].ans = gcd(t[p<<1].ans, t[p<<1|1].ans);
}
};
BIT bit;
SegmentTree st;
int main()
{
ll n, m;
cin >> n >> m;
for(int i = 1; i <= n; i++)
{
scanf("%lld", a+i);
}
for(int i = n; i >= 1; i--)
{
a[i] -= a[i-1];
}
for(int i = 1; i <= n; i++)
{
bit.add(i, a[i], n);//建立树状数组
}
st.build(1, 1, n);
for(int i = 1; i <= m; i++)
{
char buf[4];
scanf("%s", buf);
if(buf[0]=='C')//表示修改
{
ll l, r, d;
scanf("%lld%lld%lld", &l, &r, &d);
st.add(1, l, d);
if(r+1 <= n) st.add(1, r+1, -d);
bit.add(l, d, n);
if(r+1 <= n)bit.add(r+1, -d, n);
}
else//表示进行询问
{
ll l, r;
scanf("%lld%lld", &l, &r);
if(l == r) cout << bit.ask(l) << '\n';
else cout << gcd(bit.ask(l), st.ask(1, l+1, r)) << '\n';
}
}
return 0;
}
关于gcd的求法:
当把两个集合合并的时候,只是把一个集合的根节点连接到了父亲节点,并没有花费大量的时间来处理被连接的这颗子树。
在查询的时候使用路径压缩进行处理。
延迟标记主要适用于对于区间的修改。就像查询一样,当样修改的区间完全覆盖当前节点的区间的时候,然后就直接回溯,不再继续向下,把这一个节点做一个标记。当某一次查询的时候,如果需要查询这个节点的子节点,那么就
注意延迟标记的定义:当前节点已经被修改,但是子节点还没有被修改
在这道题目中,我仅仅是给当前节点增加了一个数字,并没有考虑到子区间的每一个数字全部增加,所以这个点在修改之后的值应该是区间长度乘以增加的值。
if(l <= t[p].l && t[p].r <= r)
{
t[p].add += d;
t[p].sum += d;
return;
}
#include
using namespace std;
#define N 100020
typedef long long ll;
ll s[N];
class SegmentTree{
private:
struct {
ll l, r, sum, add;//注意:add是增量延迟标记。
}t[4*N];
inline void spread(ll p)
{
if(t[p].add)
{
t[p<<1].add += t[p].add;
t[p<<1|1].add += t[p].add;
t[p<<1].sum += (t[p].add)*(t[p<<1].r-t[p<<1].l+1);
t[p<<1|1].sum += t[p].add*(t[p<<1|1].r-t[p<<1|1].l+1);
t[p].add = 0;
}
}
public:
void build(ll p, ll l, ll r)
{
t[p].l = l;
t[p].r = r;
if(l==r)
{
t[p].add = 0;//其实这一句话并没有什么实际的作用,因为在初始化的时候全部是0.
t[p].sum = s[l];
return;
}
ll mid = (l+r)>>1;
build(p<<1, l, mid);
build(p<<1|1, mid+1, r);
t[p].sum = t[p<<1].sum + t[p<<1|1].sum;
}
ll ask(ll p, ll l, ll r)
{
if(l <= t[p].l && t[p].r <= r)
return t[p].sum;
ll mid = (t[p].l+t[p].r) >> 1;
ll ans = 0;
if(l <= mid)
{
if(t[p].add) spread(p);
ans += ask(p<<1, l, r);
}
if(r > mid)
{ if(t[p].add) spread(p);
ans += ask(p<<1 | 1, l, r);
}
return ans;
}
void change_add(ll p, ll l, ll r, ll d)
{
if(l <= t[p].l && t[p].r <= r)
{
t[p].add += d;
t[p].sum += d*(t[p].r - t[p].l + 1);
return;
}
ll mid = (t[p].l + t[p].r) >> 1;
if(l <= mid)
{
if(t[p].add) spread(p);
change_add(p<<1, l, r, d);
}
if(r > mid)
{
if(t[p].add) spread(p);
change_add(p<<1|1, l, r, d);
}
t[p].sum = t[p<<1].sum + t[p<<1|1].sum;
}
};
SegmentTree t;
int main()
{
ll n, m;
scanf("%lld%lld", &n, &m);
for(int i = 1; i <= n; i++) scanf("%lld", s+i);
t.build(1, 1, n);
for(int i = 1; i <= m; i++)
{
char ch;
cin >> ch;
ll l, r;
scanf("%lld%lld", &l, &r);
if(ch=='C')//表示需要进行更改
{
ll d;
scanf("%lld", &d);
t.change_add(1, l, r, d);
}
else
{
printf("%lld\n", t.ask(1, l, r));
}
}
return 0;
}
这道题目我只会暴力。。
我在处理线段树的过程中遇到了一些问题。
我认为线段树应该按照实际的端点进行存储。但是实际上,线段树必须存储区间(因为我的cnt操作是针对区间进行的)
是
#include
using namespace std;
#define N 100009
typedef long long ll;
struct EDGE{
double x;//表示横坐标
double y, z;//表示纵坐标,其中y < z
int k;//如果是1,那么就增加这一条边,如果是-1,那么就删除这一条边
inline bool operator < (const EDGE &o)const{//别忘记两个const
return x < o.x;
}
}a[2*N];
double raw[2*N];//离散化之后的整数对应的原始值
map<double, int> val;//原始值映射到的整数值
int m;//表示纵坐标的个数
class SegmentTree{
private:
struct {
int l, r, cnt;
double len;
}t[8*N];//因为最多有2*N个纵坐标的边界
//注意线段树的l与r和离散化之后的整数之间的标记的区别
//线段树里面的叶子节点表示的是长度为1的区间,这个编号为i的区间相当于离散化之后的[i, i+1]。
//所以线段树里有m-1个叶子节点。
void getval(int p)
{
if(t[p].cnt > 0) //这里有三种关系:
//第一种:这个点已经被标记了,这个时候,就应该直接冉伟这一个点所表示的区间已经全部被标记。
//第二种:这一个点没有被标记,此时,该点不是叶子节点,向下深入
//第三种:这一个点没有标记,同时不是叶子节点,直接认为是0。
{
t[p].len = raw[t[p].r+1]-raw[t[p].l];
}
else
{
if(t[p].l == t[p].r) t[p].len = 0;
else t[p].len = t[p<<1].len + t[p<<1|1].len;
}
}
public:
void build(int p, int l, int r)
{
//建树的时候只是所有节点的len和cnt全部都是0,所以不需要进行值的回溯
t[p].l = l;
t[p].r = r;
t[p].len = 0;
t[p].cnt = 0;
if(l==r)
{
return;
}
int mid = (l+r)>>1;
build(p<<1, l, mid);
build(p<<1|1, mid+1, r);
}
void change(int p, int l, int r, int d)
{
if(l <= t[p].l && t[p].r <= r)
{
t[p].cnt += d;
getval(p);
return ;
}
int mid = (t[p].l+t[p].r)>>1;
if(l <= mid) change(p<<1, l, r, d);
if(r > mid) change(p<<1|1, l, r, d);
getval(p);
}
double ask()
{
return t[1].len;
}
};
SegmentTree t;
int asdf= 0;//计数器
void solve(int n)
{
double ans = 0;//一定要注意这一个答案是double类型的
for(int i = 1; i <= n; i++)
{
int k = i << 1;
double y, z;
scanf("%lf%lf%lf%lf", &a[k-1].x, &y, &a[k].x, &z);// ******************!!!
a[k-1].y = a[k].y = y;
a[k].z = a[k-1].z = z;
raw[k-1] = y;
raw[k] = z;
a[k-1].k = 1;
a[k].k = -1;
}
sort(raw+1, raw+1+2*n);
m = unique(raw+1, raw+1+2*n)-(raw+1);//unique返回的值是去重之后区域的尾指针。
for(int i = 1; i <= m; i++) val[raw[i]] = i;
sort(a+1, a+1+2*n);
t.build(1, 1, m-1);// *********!!!!
for(int i = 1; i <= 2*n; i++)
{
ans += (a[i].x-a[i-1].x)*t.ask();// ********************
//cout << "debug" << val[a[i].y] << " " << val[a[i].z-1];
//fflush(stdout);
t.change(1, val[a[i].y], val[a[i].z]-1, a[i].k);
}
printf("Test case #%d\nTotal explored area: %.2lf\n",++asdf, ans);
puts("");
}
int main()
{
int n;
while(scanf("%d", &n) && n) solve(n);
return 0;
}
如代码部分所示。
要注意,在这一道题目中,线段树是区间修改,按照道理,应该使用延迟标记来进行加快速度。
但是由于:
所以可以不适用延迟标记采用getval()
函数内部的方法。
输入样例:
3 5 4
1 2 3
2 3 2
6 3 1
3 5 4
1 2 3
2 3 2
5 3 1
输出样例:
5
6
这道题目就很像前缀和的板子题。
就是坐标的范围有一点大,但是可以进行离散化,然后就能统计前缀和,得到答案。
当然,这里使用线段树。
使用线段树需要迈过两个关卡
关卡二:我框星星 or 星星自己来一个框
我框住的星星的亮度的最大值就等价于把每一个星星作为左下角,然后生成宽W-1
,高H-1
的亮度为c边框,所有边框放在一起,(亮度可以叠加),找到一点最亮的点。
证明:
两个集合等价,所以最大值也相等。
得证!
根据上一道题的做法,写代码不成问题!
注意:上一个题目是没有用到延迟标记(用了延迟标记的一半),但是这一道题目不能与上一道题目一样。
这里必须完整的延迟标记。
#include
using namespace std;
typedef long long ll;
#define N 10005
#define lc (p<<1)
#define rc (p<<1|1)
ll raw[N*2];//离散化之后的值 ———> 离散化之前的值
map<ll, ll> val;//离散化之前的值------> 离散化之后的值
struct AABS{
ll x, y, z, c;
inline bool operator < (const AABS &o) const{
return x < o.x || (x == o.x && c > o.c);// 注意我的第二关键字!!!!!!!!
//因为边界上的也算,
//假设有以下情况:在边界x上有一条边是$c = 5$,另一条边是$c = -3$,这个时候,应该先加上C大的(c小的在这里仍然起作用)
}
}a[N*2];
class SegmentTree{
private:
struct {
ll l, r, ans, add;
}t[N*8];
inline void spread(ll p)
{
if(t[p].add == 0) return;
t[lc].add += t[p].add;
t[rc].add += t[p].add;
t[lc].ans += t[p].add;
t[rc].ans += t[p].add;//这里的spread就是简单的+=
t[p].add = 0;
}
public:
void build(ll p, ll l, ll r)
{
t[p].l = l;
t[p].r = r;
t[p].ans = 0;
t[p].add = 0;//全部需要归零
if(l==r)
{
return ;
}
ll mid = (l+r)>>1;
build(lc, l, mid);
build(rc, mid+1, r);
}
void change(ll p, ll l, ll r, ll d)
{
if(l<= t[p].l&& r >= t[p].r)
{
t[p].ans += d;
t[p].add += d;
return ;
}
ll mid = (t[p].l + t[p].r)>>1;
spread(p);
if(l <= mid) change(lc, l, r, d);
if(r > mid) change(rc, l, r, d);
t[p].ans = max(t[lc].ans, t[rc].ans);
}
ll ask(ll p, ll l, ll r)
{//其实针对所有区间的(不用这样麻烦)~ ~ ~ ~ ~
if(l <= t[p].l && t[p].r <= r)
{
return t[p].ans;
}
ll mid = (t[p].l + t[p].r) >> 1;
ll ret = -0x3f3f3f3f3f3f3f;
if(l <= mid) ret = max(ret, ask(lc, l, r));
if(r > mid) ret = max(ret, ask(lc, l, r));
return ret;
}
};
SegmentTree t;
void solve(int n, int W, int H)
{
ll ans = 0;
val.clear();
for(int i = 1; i <= n; i++)
{
ll x, y, c;
scanf("%lld%lld%lld", &x, &y, &c);
x++;//注意还有x==0 的情况,所以我在这里加1以防止越界!!!
y++;
ll k = i<<1;
a[k-1].x = x;
a[k].x = x+W-1;// 还可以是另一种组合:
//是a[k].x = x+W; 并且 最一开始的a的比较应该是把c小的放在前面(两种全部可以AC)
a[k].y = a[k-1].y = y;
a[k].z = a[k-1].z = y+H-1;
a[k-1].c = c;
a[k].c = -c;
raw[k-1] = y;
raw[k] = y+H-1;
}
sort(a+1, a+1+2*n);
sort(raw+1, raw+1+2*n);
ll m = unique(raw+1, raw+1+n*2)-(raw+1);
for(int i = 1; i <= m; i++)
{
val[raw[i]] = i;
}
t.build(1, 1, m);//注意:这里是维护的点的最大值
//注意:这里需要建立m个
for(int i = 1; i <= 2*n; i++)
{
t.change(1, val[a[i].y], val[a[i].z], a[i].c);
ans = max(ans, t.ask(1, 1, m));
}
cout << ans << "\n";
}
int main()
{
int n, H, W;
while(cin >> n >> W >> H) solve(n, W, H);
return 0;
}
线段树用来维护一段的权值范围:称为权值线段树。
动态开点的意义:
降低空间复杂度
l
和 r
依靠区间进行传递。#include
using namespace std;
#define N 100
struct Tree{
int lc, rc, ans;
}t[2*N];
int tot = 0;
int root;
//所需要的三要素:
//1.大小为2*N的结构体
//2.tot表示计数
//3.root表示根节点
int build()//作用为新建一个节点
{
tot++;
t[tot].ans = t[tot].lc = t[tot].rc = 0;
return tot;
}
int main()
{
tot = 0;
root = build(); //这两步相当于是初始化
return 0;
}
void add(int p, int l, int r, int val, int delta)//作用:把val位置增加delta
{
if(l==r)
{
t[p].ans += delta;
return ;
}
int mid = (l+r)>>1;
if(val <= mid)
{
if(!t[p].lc) t[p].lc = build();
add(t[p].lc, l, mid, val, delta);
}
else
{
if(!t[p].rc) t[p].rc = build();
add(t[p].rc, mid+1, r, val, delta);
}
t[p].ans = max(t[t[p].lc].ans, t[t[p].rc].ans);
//注意:当某一个孩子不存在的时候,访问的是t[0],注意把t[0].ans 设置合适的值
}
两个线段树要维护相同的总区间
注意:仅仅适用于
以下代码表示得到把两个线段树维护的数列相加之后的线段树。
如果直接暴力操作比较麻烦,直接线段树合并比较容易
int Merge(int p, int q, int l,int r)
{
if(!q) return p;
if(!p) return q;
if(l == r)
{
t[p].ans += t[q].ans;
return p;
}
int mid = (l+r)>>1;
t[p].lc = Merge(t[p].lc, t[q].lc, l, mid);
t[p].rc = Merge(t[p].rc, t[q].rc, mid+1, r);
t[p].ans = max(t[t[p].lc].ans, t[t[p].rc].ans);//不要忘记更新值
return p;//不要忘记返回
}
由于在合并的过程中,每一次调用Merge
函数都会删除一个节点。所以时间复杂度为 M l o g N MlogN MlogN,其中M是插入的次数,N是区间的长度。时间复杂度最多不超过2*N。
完!