数据结构 【树状数组】 【线段树】 【珂朵莉树】
数据结构
一.区间合并
1.AcWing245你能回答这些问题吗


分析:
线段树。维护四个变量,即可实现区间合并。
mx 区间最大连续子段和
mx_l 以区间左端点为左界的最大连续字段和
mx_r 以区间左端点为左界的最大连续字段和
sum 区间和
AC代码:
1 |
|
2.Codeforces Round #742 (Div. 2) E. Non-Decreasing Dilemma


题意:
长度为n的数组,q次询问,每次询问三个参数op,x,y
op==1,ar[x]=y;
op==2,查询区间内有多少个不降的子数组。
分析:
同上一个题一样,除了维护询问的属性sum以外,再维护子数组左右边界分别为区间边界时最长的长度len_l,len_r,,以及区间长度len,即可实现区间合并。
AC代码:
1 |
|
二.珂朵莉树
牛客练习赛100 E 小红的公倍数(珂朵莉树)


分析:
看题目特征,首先,题目数据随机生成,其次,存在区间推平操作,故可以考虑珂朵莉树。
另外,需要注意 $lcm(a,b) \% mod \neq lcm(a\%mod, b)$。故直接维护区间lcm是行不通的,由此我们质因数分解,维护每个质因数在区间内最多出现多少次方。20000以内的质数个数不是很多,不到2300个,再考虑的推平操作,考虑使用珂朵莉树。(线段树的话,由于要开4倍的空间,所以还是有点卡的)
AC代码:
1 |
|
三.区间操作
1.2022 ACM 湖北省赛 L (线段树)
题意:
输入长度为n的数组,q次询问,n个数字每个数字有两个权值,权值ar[i] (1 <= ar[i] <= 10^8),s[i](s[i]=0/1)。询问分为一下四种。
1 x : 将s[x]变成0,保证原来的 s[x] = 1
2 x : 将s[x]变成1,保证原来的 s[x] = 0
3 L R x : 给区间 [l,r] 中所有的s[i]=1的ar[i]都加上一个数
4 L R : 输出区间 [l,r] 中的 ar[i] 的和
分析:
维护$\sum ar[i]$和$\sum s[i]$即可,由于是区间修改,所以还需要懒标记。
AC代码:
1 |
|
2.2022牛客寒假算法基础集训营4 B-进制 (线段树)



分析:
维护区间最大值,和作为i进制数的大小即可维护。
AC代码:
1 |
|
3.Codeforces Round #254 (Div. 1) C. DZY Loves Colors (线段树)

分析:
维护区间权值和,区间的颜色,如果区间内颜色统一则区间修改,使用lazy标记,不统一则暴力单点修改。
AC代码:
1 |
|
四.推结论+数据结构
1.同济大学程序设计竞赛 G(离线+树状数组)
另外,这场比赛的D题 两串糖果,区间DP

分析:
注意到数据范围,k>ar[i] ,那要么把ar[i]变成0,要么变成k。
对于一次询问[l,r,k]。
对于区间内满足ar[i] <= k/2的元素ar[i],对答案的贡献是:$\sum ar[i]$
对于剩下的元素ar[i],对答案的贡献是:$cnt \times k - \sum ar[i]$。其中 $cnt$ 是区间内大于等于k/2的元素个数,假设cnt1是区间中满足ar[i]<=k/2的个数。则cnt = r - l + 1 - cnt1。
对于上面的几个变量如何求解,我们可以先考虑下面这个问题。
daimayuan464. 数数 【离线+离散化+树状数组】
做完这个题,容易发现上面所说的cnt1就是这个题所求解的量,即区间内小于等于某个数的个数,而对于区间内小于等于某个数的数字之和,我们可以利用同样的方法求解,只需要再维护一颗树状数组,维护的是前缀和。
AC代码:
1 |
|
2.daimayuan 喵 ~ 喵~ 喵 ~

分析:
每次添加操作相当于添加一个区间,询问相当于问你与这个区间有交集的区间的个数。
那么对于一次询问[L,R]相当于,与区间[1,R]有交集的区间个数cnt1减去完全在L-1及其左侧的区间个数cnt2。
cnt1即在此之前有多少个小于等于R的 l
cnt2即在此之前有多少个小于等于L - 1的r
开两个树状数组维护即可。
AC代码:
1 |
|
3.Educational Codeforces Round 126 (Rated for Div. 2) D. Progressions Covering


等差数列,结合差分,可以线段树维护
1 |
|
更简单的方法,深入理解一下等差数列,一个队列即可维护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
using namespace std;
typedef long long ll;
const int maxn = 4e5 + 5;
const ll mod = 1e9 + 7;
const ll inf = 0x3f3f3f3f;
int n, k, cnt, len;
int ar[maxn];
queue<pii> q;
int ans;
signed main()
{
io;
cin >> n >> k;
for(int i = 1; i <= n; ++i) cin >> ar[i];
int sum = 0, sum_cnt = 0;
for(int i = n; i > 0; --i)
{
sum -= sum_cnt;
ar[i] -= sum;
if(ar[i] > 0)
{
len = min(i, k);
cnt = (ar[i] + len - 1ll) / len;
q.push({i - len + 1ll, cnt});
sum += len * cnt;
sum_cnt += cnt;
ans += cnt;
}
if(q.front().first == i)
{
sum -= q.front().second;
sum_cnt -= q.front().second;
q.pop();
}
}
cout << ans << '\n';
return 0;
}
五.dp/思维+数据结构
1.最长上升子序列计数(Bonus)(DP/线段树/树状数组)


分析:
对于数据没加强版
直接暴力dp即可,代码长这个样子。
一方面,我们需要优化这份代码,另一方面我们需要根据这个的递推确定如何利用一些数据结构递推,维护什么样的数据结构。
很明显需要优化j那个循环,即对于每个位置i,我们需要快速的找到前面的dp最大值,和其对应的子序列数量。
做法是,进行离散化,维护的是以i(离散化后)为结尾的最长上升子序列长度,和数量。
对于i(离散化后),我们要求解是就需要query(1,i-1),据此递推,主要确定sum的递推,是直接覆盖还是累加,另外,初识化和边界也是特别难调的。
AC代码:
1 |
|
2.Codeforces Round #783 (Div. 2) D. Optimal Partition 【dp+线段树】
题意:
分析:
首先,很容易想到暴力的dp。
定义dp[i]为将1~i分完的最大值,状态转移方程dp[i] = max(dp[i], dp[j] + sign(sum[j, i]) * (i - (j + 1) + 1)),复杂度是$O(n^2)$,考虑优化。
将方程具体写开if(sum[j] == sum[i]) dp[i] = max(dp[i], dp[j]);if(sum[j] > sum[i]) dp[i] = max(dp[i], dp[j] + j - i);if(sum[j] < sum[i]) dp[i] = max(dp[i],dp[j] - j + i);
即对于dp[i]
我们需要所有满足sum[j] == sum[i]的max(dp[j])
所有满足sum[j] > sum[i]的max(dp[j] + j)
所有满足sum[j] < sum[i]的max(dp[j] - j)
dp[i]可以由上面的3个值转移过来。
需要离散化,维护三个区间最大值即可。复杂度$O(nlogn)$。
AC代码:
1 |
|
3.Codeforces Round #609 (Div. 1) C. K Integers
题意:
分析:
很明显答案分为两部分,一是对应区间的逆序对的个数cnt1,二是将不应该存在的数字移出去区间最少需要操作多少次cnt2(等于将这k个数字挤在一起最少需要操作多少次)。
cnt1即求逆序对,树状数组维护即可。
对于cnt2,我们考虑将这k个数字挤在一起最少需要操作多少次。
假设1~k这k个数的位置中,中间那个数字的位置是mid,mid及其左侧一共cnt个数字。
对于mid左侧的第1个数字,需要移动到mid,需要mid-pos1次操作 (posi表示mid左侧第i个数字的位置)
第2个,需要移动到mid-1的位置,需要mid-1-pos2次操作
第3个,需要移动到mid-1的位置,需要mid-1-pos3次操作
则左侧一共需要的操作次数是$mid \times cnt - cnt \times (cnt - 1) / 2 - \sum_{i=1}^{cnt} posi$
右侧同理。
利用两个树状数组即可维护。
AC代码:
1 |
|