首先,这个问题借助乘法逆元,可以非常优美地解决,具体请参考零神的题解:https://leetcode.cn/problems/fancy-sequence/solution/qi-miao-xu-lie-by-zerotrac2/
经常玩儿算法比赛的同学,还是需要学习乘法逆元的。乘法逆元的在算法竞赛中使用非常广泛,但在面试中近乎没有用。鉴于现在力扣的问题越来越竞赛化,所以学习乘法逆元不吃亏。
但是,对于这个问题,如果你不会乘法逆元,也是可以解决的。由于问题的操作都是在一个区间中进行的,所以很容易想到使用线段树求解。
其实树状数组也可以,不过因为力扣官方网站本身有非常好的线段树的解析,所以,这个题解的代码,我使用线段树。
另外值得一提的是,使用线段树解决,其实可以解决这个问题的“更强版本”。在这个问题中,每次操作都是对已经有的所有数据进行的。但是如果问题变成,在指定的某个区间,所有的元素做乘法或者加法,既支持的是 addRange(l, r, inc) 和 multRange(l, r, m) 两种操作的话,线段树也可以轻松应对。(可以出一个“奇妙序列 II”了。)
因为这个问题需要区间更新,所以,需要使用线段树的懒更新功能。强烈建议大家首先仔细阅读学习这份力扣的文档,在这份文档的后续,详细介绍了如何为线段树添加懒更新的功能,并且附有代码:https://leetcode.com/articles/a-recursive-approach-to-segment-trees-range-sum-queries-lazy-propagation/
下面,我对这个问题的解题代码,将直接基于这篇文章的线段树代码进行修改。
上面的文章中,解决了如果为指定区间所有元素添加一个值,怎样进行区间更新。但是在这个问题中,还需要处理,如果为指定区间所有元素乘以一个值,要怎么办?
首先,非常关键的一点是,经过一系列题目中的操作,对于每一个元素 x,最终都可以化为 x * a + b 的形式。
比如,对于某个 x,我们有了 ((x + inc1) * m1 + inc2) * m2 + inc3,展开,就有了:
x * (m1 * m2) + (inc1 * m1 * m2 + inc2 * m2 + inc3)。
相当于是 a = m1 * m2;b = inc1 * m1 * m2 + inc2 * m2 + inc3。
因此,我们在线段树的懒标记中,只需要记录经过若干操作以后,对每个元素的 a 是多少,b 是多少,就好了。
所以,我们的线段树需要两个懒标记数组,我使用 lazym 和 lazya,来记录乘数和加数。
下面的关键就是,如果来了一个运算,如何做懒更新?
假设本来的元素是 x,对应的乘数是 a,加数是 b。也就是其实应该是 ax + b。
来了一个乘法运算 m。则变成了 (ax + b) * m = amx + bm。可以看到,乘数和加数都要乘以 m;
来了一个加法运算 inc,则变成了 ax + b + inc = ax + (b + inc)。可以看到,只要加数加 m 就好了。
在下面的代码中,注释有“懒更新”字眼的代码,是实现的这部分逻辑。
除此之外,还有一个问题,懒更新除了更新下面的 lazym 和 lazya 数组,还需要更新当前的区间 tree[treeID]。
这其实很简单,因为当前区间的所有元素都要乘以 a 然后加上 b。所以整体就是对原来的区间和乘以 a,然后加上区间长度 len * b(每个元素都加上了 b)。
在下面的代码中,注释有“区间更新”字眼的代码,是实现的这部分逻辑。
取此之外,所有的逻辑都和上面文章中的线段树是一样的。
参考代码(C++):
class SegmentTree{
private:
int n;
vector<long long> tree, lazya, lazym;
const long long MOD = 1e9 + 7;
public:
SegmentTree(int n): n(n), tree(4 * n, 0ll), lazya(4 * n, 0ll), lazym(4 * n, 1ll){}
void add(int index, int val){
update(0, 0, n - 1, index, index, val, 1);
}
void update_add(int uL, int uR, int inc){
update(0, 0, n-1, uL, uR, inc, 1);
}
void update_mul(int uL, int uR, int mul){
update(0, 0, n-1, uL, uR, 0, mul);
}
int query(int index){
return query(0, 0, n-1, index);
}
private:
void update(int treeID, int treeL, int treeR, int uL, int uR, int inc, int mul){
if(lazya[treeID] != 0 || lazym[treeID] != 1){
// 区间更新
tree[treeID] = (tree[treeID] * lazym[treeID] + (treeR - treeL + 1)* lazya[treeID]) % MOD;
// 懒更新
if(treeL != treeR){
lazym[2 * treeID + 1] = lazym[2 * treeID + 1] * lazym[treeID] % MOD;
lazya[2 * treeID + 1] = lazya[2 * treeID + 1] * lazym[treeID] % MOD;
lazya[2 * treeID + 1] = (lazya[2 * treeID + 1] + lazya[treeID]) % MOD;
lazym[2 * treeID + 2] = lazym[2 * treeID + 2] * lazym[treeID] % MOD;
lazya[2 * treeID + 2] = lazya[2 * treeID + 2] * lazym[treeID] % MOD;
lazya[2 * treeID + 2] = (lazya[2 * treeID + 2] + lazya[treeID]) % MOD;
}
lazya[treeID] = 0;
lazym[treeID] = 1;
}
if (treeL > uR || treeR < uL) return;
if(uL <= treeL && uR >= treeR){
// 区间更新
tree[treeID] = (tree[treeID] + tree[treeID] * (mul - 1) + (treeR - treeL + 1) * inc) % MOD;
// 懒更新
if(treeL != treeR){
lazym[2 * treeID + 1] = lazym[2 * treeID + 1] * mul % MOD;
lazya[2 * treeID + 1] = lazya[2 * treeID + 1] * mul % MOD;
lazya[2 * treeID + 1] = (lazya[2 * treeID + 1] + inc) % MOD;
lazym[2 * treeID + 2] = lazym[2 * treeID + 2] * mul % MOD;
lazya[2 * treeID + 2] = lazya[2 * treeID + 2] * mul % MOD;
lazya[2 * treeID + 2] = (lazya[2 * treeID + 2] + inc) % MOD;
}
return;
}
int mid = (treeL + treeR) / 2;
update(2 * treeID + 1, treeL, mid, uL, uR, inc, mul);
update(2 * treeID + 2, mid + 1, treeR, uL, uR, inc, mul);
tree[treeID] = (tree[treeID * 2 + 1] + tree[treeID * 2 + 2]) % MOD;
return;
}
int query(int treeID, int treeL, int treeR, int index){
if(lazya[treeID] != 0 || lazym[treeID] != 1){
// 区间更新
tree[treeID] = (tree[treeID] * lazym[treeID] + (treeR - treeL + 1)* lazya[treeID]) % MOD;
// 懒更新
if(treeL != treeR){
lazym[2 * treeID + 1] = lazym[2 * treeID + 1] * lazym[treeID] % MOD;
lazya[2 * treeID + 1] = lazya[2 * treeID + 1] * lazym[treeID] % MOD;
lazya[2 * treeID + 1] = (lazya[2 * treeID + 1] + lazya[treeID]) % MOD;
lazym[2 * treeID + 2] = lazym[2 * treeID + 2] * lazym[treeID] % MOD;
lazya[2 * treeID + 2] = lazya[2 * treeID + 2] * lazym[treeID] % MOD;
lazya[2 * treeID + 2] = (lazya[2 * treeID + 2] + lazya[treeID]) % MOD;
}
lazya[treeID] = 0;
lazym[treeID] = 1;
}
if(treeL == treeR) return tree[treeID];
int mid = (treeL + treeR) / 2;
if(index <= mid) return query(2 * treeID + 1, treeL, mid, index);
return query(2 * treeID + 2, mid + 1, treeR, index);
}
};
有了这样一个线段树,题目中要求的 Fancy 类是非常简单的:
class Fancy {
private:
SegmentTree tree;
int len = 0;
public:
Fancy() : tree(1e5 + 1){}
void append(int val) {
tree.add(len ++, val);
}
void addAll(int inc) {
tree.update_add(0, len - 1, inc);
}
void multAll(int mul) {
tree.update_mul(0, len - 1, mul);
}
int getIndex(int idx) {
if(idx >= 0 && idx < len)
return tree.query(idx);
return -1;
}
};
对于这个方法,所有的操作都是 O(logn) 的。
依然是,这个方法的优点是,如果问题要求在指定的某个区间做乘法或者加法,即支持的是 addRange(l, r, inc) 和 multRange(l, r, m) 两种操作的话,这个解法也能轻松应对。
觉得有帮助请点赞哇!