线段树+树状数组
线段树大C
树状数组与线段树总结
时间复杂度
虽然它们都是nlogn,但是,你会发现,在查询时,树状数组最坏情况是logn(比如8个数,然后查询8),但是线段树是所有情况都是nlogn,稍慢于树状数组。
空间复杂度
树状数组完胜于线段树,线段树要开2倍到4倍内存(推荐4倍),但是树状数组一倍就够了。
适用范围
线段树之所以存在的理由是因为它能适用于很多方面,不仅仅是区间、单点的查询修改,还有标记等等,可以用于模拟、DP等等,而且空间经过离散化以后也可以相对压缩,所以适用范围线段树更加广一些。
树状数组
树状数组单点修改
1 |
|
线段树
线段树单点修改
1 | //注意保证l<r及数据的合法性 |
线段树区间修改
1 | //注意保证l<r及数据的合法性 |
线段树扫描线
1 | //没有下放操作,在cnt=1时不会错,cnt=2时会错,即如果求矩形面积的并不会错,求至少覆盖两次的会出错 |
4⭐树状数组统计ai之前比ai小的数个数
题意:给一个长度为n<5e4的数列,每个数编号从1到n,且无重复。一个四元组(a,b,c,d),要求a<b<c<d 且 Aa<Ab && Ac<Ad,问数列中这样的四元组有多少个。
思路:关键在于如何统计数列中在ai之前且比ai小的数有多少个。用树状数组统计,代码如下:
for(ll i=1;i<=n;i++) re[i]=sum(ar[i]),add(ar[i],1);
知道这点后,对于四元组中每个b,其四元组个数为(b之前小于b的个数)*(b之后的(c,d)对数),对于(c,d)对数统计,即倒过来再扫一遍即可。
1 |
|
线段树基本运用(有另类巧妙做法)
题意 有 t 组数据,每组有 n 张(1<=n<=1e4)覆盖了 区间 [li,ri] 的海报(1<=i<=n,1<=li<=ri<=1e7),海报会由于张贴顺序先后而被覆盖 问还有多少张海报能够看得见
优质题解
点击查看代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
//两种做法
//dfs做法
using namespace std;
typedef long long ll;
const ll maxn=1e5+11;
ll li[maxn],ri[maxn],vis[maxn],ans;
void dfs(ll l,ll r,ll x){
if(x==0) return ;
if(l<=ri[x] && li[x]<=r){
if(!vis[x]){
vis[x]=1;ans++;
}
if(l<li[x]) dfs(l,li[x]-1,x-1);
if(r>ri[x]) dfs(ri[x]+1,r,x-1);
}
else dfs(l,r,x-1);
}
void init(){
memset(vis,0,sizeof(vis));
ans=0;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
ll t;cin>>t;
while(t--){
init();
ll n;cin>>n;
for(ll i=1;i<=n;i++) cin>>li[i]>>ri[i];
dfs(1,1e7,n);
cout<<ans<<endl;
}
return 0;
}
//线段树做法
using namespace std;
typedef long long ll;
const ll maxn=3e4+11;
ll t[maxn<<2],vis[maxn<<2],x[maxn<<2],a[maxn<<2],b[maxn<<2],ans,step;
void push_down(ll now){
if(t[now]){
t[now<<1]=t[now<<1|1]=t[now];
t[now]=0;
}
}
void update(ll L,ll R,ll val,ll l,ll r,ll now){
if(L<=l && r<=R){
t[now]=val;return ;
}
push_down(now);
ll m=l+((r-l)>>1);
if(L<=m) update(L,R,val,lson);
if(R>m) update(L,R,val,rson);
}
void query(ll l,ll r,ll now){
if(t[now] && !vis[t[now]]){
vis[t[now]]=1;ans++;
return ;
}
if(l==r) return ;
push_down(now);
ll m=l+((r-l)>>1);
query(lson);query(rson);
}
ll pos(ll xi){
return lower_bound(x+1,x+1+step,xi)-x;
}
void init(){
memset(vis,0,sizeof(vis));
memset(t,0,sizeof(t));
}
int main(){
ll ta;cin>>ta;
while(ta--){
init();
ll n;step=1;cin>>n;
for(ll i=1;i<=n;i++){
cin>>a[i]>>b[i];
if(b[i]-a[i]>1) x[step++]=a[i]+1;
x[step++]=a[i];x[step++]=b[i];
}
sort(x+1,x+step);
step=unique(x+1,x+step)-(x+1);
for(ll i=1;i<=n;i++){
update(pos(a[i]),pos(b[i]),i,1,step,1);
}
ans=0;query(1,step,1);
cout<<ans<<endl;
}
return 0;
}
线段树基本运用(有另类巧妙做法)
题意
n个点,m个操作(1<=n,m<=5e4),m行中每行一个字符c表示操作类型,一个整型数x表示第x个点
D x 表示去掉第x点,Q x表示需输出含 x 的最大连续区间的长度,R x表示还原最后去掉的点
线段树灵活运用(1700)
题意
t 组数据(t<=10),每组第一行一个 n 表示 n 个员工(n<=5e4),接下来 n-1 行,每行两个整数 u,v 表示 v 是 u 的上司
然后一行 m 表示有 m 个操作(m<=5e4) , 接下来 m 行 由一个字符 和 整型数构成 ,表示不同操作
C x 表示 输出 第 x 个员工的工作号
T x y 表示 将 第 x 个员工的工作号变为 y (x<=n,0<=y<=1e9)
规定,改变工作时,被改变员工的下属一同变为 y
优质题解
注意用线段树求某点对应区间时,只需要在dfs序时,记录其区间左端点和右端点即可,没必要再拉个深度d数组,再二分找
线段树灵活运用-不确定区间
题意:有n<5e4个花瓶,编号0n-1,有m<5e4次操作,操作1:从a开始放f个花瓶,输出花瓶放置的第一个位置和最后一个位置,操作2:拿掉ab之间的花瓶,输出拿掉的花瓶的数量
思路:由于这题是从a开始放f个花瓶,那么与以往线段树不同的一点是,这个区间的右端点是不确定的,得我们自己确定这个区间的右端点。虽然最容易想到的是在线段树里模拟放置花瓶,但是这肯定是不行的。第一,线段树的一般用法不是这么用的,第二,模拟的细节巨多,耗时,炸心,不可能写出来。那我们的方法就是通过二分查找出这个区间的右端点即可,还有一个点就是还需要找到放置花瓶的起始位置和最后一个位置,剩下的就是基础线段树操作。这道题还是需要线段树较扎实的功底的。
1 | /* |
线段树-找最近小
The 2021 ICPC Asia Regionals Online Contest A Busiest Computing Nodes
题意:有k<1e5个工厂,从0k-1编号,有n个任务,从0n-1编号,每个任务有一个到达时间和一个处理时间。每个任务到达时,若其任务编号为i,则先从第i%k个工厂开始往后找,i+1%k,i+2%k…..一直找到i-1%k,如果所有工厂都不空闲,则此任务被丢弃,问处理任务最多的工厂是哪些。
点击查看思路
思路:即找最近小的t,满足arrival_time(记为ta) < t,那么因为这个是最近小,距离是第一优先级,就不能用单调队列那些,因为不是按t排序的。这里就要用到二分查找。查找分为两个区间,一个是[i%k,n-1],另一个是[0,i-1%k],前者记为a区间,后者记为b区间。先找到a区间的最小值,如果最小值(记为minn)都>ta,则找b区间。否则“先”在a区间的左半区间找,若minn>ta,则找a的右半区间找。即先找每个区间的左区间,左区间没有再找右区间即可。是一个二分的思想。用线段树维护区间最小值即可。我一开始也没想到二分还能这么用,因为我看这道题t不是单调的,但是现在发现距离是单调的,所以查找最近小还是可以用二分。
1 | /* |
Segment Tree beats 维护一个数列
题意
这里对于100以内的25个质数的维护,可以状态压缩,即用一个int的一位二进制表示改质数是否存在。也就是再拉个线段树维护25个质数。
1 |
|
线段树灵活运用
n,q < 1e5;
思路:容易想到,维护一个区间和,再维护一个区间的一样长的串的长度,若区间内串长度不一样,则为-1.
按照这种想法,在update的时候,需要串长一样才能更新,这样子复杂度较大,导致TLE。
优化:思考如何做到在一个区间内串长不一样时,照样可以更新呢?
通过公式推导,
这里d1,d2推广到加的前缀和加的后缀,而不是单单一个十进制数字d,是因为lazy的懒惰性(叠加性),必然会使得这个d越来越长。故我们直接推广到d1,d2。
接下来代码方面就不难了。
1 | /* |