题目描述
请你实现三个 API append,addAll 和 multAll 来实现奇妙序列。
请实现 Fancy 类 :
- Fancy() 初始化一个空序列对象。
- void append(val) 将整数 val 添加在序列末尾。
- void addAll(inc) 将所有序列中的现有数值都增加 inc 。
- void multAll(m) 将序列中的所有现有数值都乘以整数 m 。
- int getIndex(idx) 得到下标为 idx 处的数值(下标从 0 开始),并将结果对 109 + 7 取余。如果下标大于等于序列的长度,请返回 -1 。
题目链接力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
思路
本题难点在于通过时间设限和数值太大溢出问题,如果采用暴力解法,在每次addAll和multAll时遍历所有元素,那时间复杂度为O(n),会无法通过本题,对于值溢出的情况,需要在每次涉及到运算的地方取模P,也是为了符合题目要求,先考虑初步的思路,目前只考虑在data数组中已元素且在此之前还没有进行任何addAll和multAll操作的情况,此时再开始进行加与乘操作,如果想在O(1)的情况下完成上面的函数,可以通过初等变换一个二阶方阵A来实现上述的加与乘操作的记录,a为待加元素,m为待乘元素
此时获取元素值时,将矩阵A中的a和m取出与数组data中的元素e运算,为了防止溢出,本题后续所有运算后都会取模P,即有下面的等式
初始时待乘变量为1,待加变量0,因此该方阵初始时如下
如果要进行加c操作,就让其左乘一个初等加矩阵
如果是乘c操作,就让其数左乘一个初等乘矩阵
通过简单的线性变换来降低addAll和multAll的时间复杂度,这是初步思路,但是方阵A会随着addAll和multAll不断变化,data中的元素数量也是动态递增的,不难看出,data中放入的元素e如果要获得它的待加元素和待乘元素,只有在其被加入之后的新的addAll和multAll操作才是有效的,如果元素ei加入之前的方阵为Ai,当前最新的方阵为An,那就需要解出通过新的加与乘的线性变换方阵B,有
即
解出矩阵
为
这样就得到了每一个元素ei的待加元素和待乘元素
,其中
为
的逆元,因为本题中所有的运算都有对P取余的缘故,
,对于求逆元,有两种算法,一种是费马小定理,若P为素数,gcd(m,p)=1,则
要求m的逆元就有
显然本题中的P为素数,要求,可以采用快速幂算法,时间复杂度为O(logP),本题C++代码如下
long Pinv(long m) {
long ans = 1, p = P - 2;
while (p) {
if (p & 1)
ans = ans * m % P;
m = m * m % P;
p >>= 1;
}
return ans;
}
另一种方法是用扩展欧几里算法来求逆元,本题使用的就是该算法,通过裴蜀定理可知, ∃x,y∈Z,使得
题中gcd(m,P)≡1,不难看出,对公式(3-11)两边取余时有,这里的t就是本题要求的逆元,同样采用递推的思路来减少时间和空间开销,为了实现递推,可以设
,
,商为q有
到qn时结束循环,根据公式(3-11)有集合S={rᵢ|rᵢ=sᵢr₀+tᵢr₁, i∈Z},因此,
,可以通过递推归纳法来得到sᵢ的前后关系
将1)和2)代入推出下面的r2
整理得如下方程
其中不为零,故
,
,由归纳法得到
,
,有了s和t的表达式,即可得出扩展欧几里得算法的递推形式
long Einv(long m) {
long x = 1, y = 0, p = P, temp;
while (m) {
temp = x;
x = y - x * (p / m);
y = temp;
temp = p % m;
p = m;
m = temp;
}
return (y < 0) ? y + P : y;
}
该算法的时间复杂度为O(log min{m,P}),因此相较于费马小定理的快速幂,可以更快的得出逆元,本题在append函数实现求逆操作并整合到data中,并且在getIndex中获取和
与data所在下标中的元素参与运算得到最终结果,要实现它,根据本题公式(3-8)有
因此append函数中要实现的计算就是
在getIndex函数中要实现的计算就是
通过上面的解法,可以在O(log min{m,P})的时间内完成append操作,而其余的操作均为O(1),费马小定理快速幂求逆元也能在O(logP)的时间内完成append两者各有优劣。
代码
#include <iostream>
#include <vector>
using namespace std;
class Fancy {
private:
static const long P = 1000000007;
vector<long> data;
long mult, add;
public:
Fancy() {
mult = 1;
add = 0;
}
// 扩展欧几里得算法求逆元
long Einv(long m) {
long x = 1, y = 0, p = P, temp;
while (m) {
temp = x;
x = y - x * (p / m);
y = temp;
temp = p % m;
p = m;
m = temp;
}
return (y < 0) ? y + P : y;
}
//费马小定理快速幂求逆元
long Pinv(long m) {
long ans = 1, p = P - 2;
while (p) {
if (p & 1)
ans = ans * m % P;
m = m * m % P;
p >>= 1;
}
return ans;
}
void append(int val) {
data.push_back((val - add + P) % P * Einv(mult) % P);
}
void addAll(int inc) {
add = (add + inc) % P;
}
void multAll(int m) {
mult = mult * m % P;
add = add * m % P;
}
int getIndex(int idx) {
if (idx >= data.size())
return -1;
return (data[idx] * mult % P + add) % P;
}
};