LC 1622. 奇妙序列
https://leetcode-cn.com/problems/fancy-sequence/
这题让我想起了分布式系统里面的回放日志操作:我们将所有的操作以日志的方式记录下来,并且日志上包含具体的时间戳。当需要确定数组某个idx的值的时候,查找到应该从哪个日志点开始放回,然后apply日志即可。
按照这种思路实现如下:
- op字段分别表示
- idx 表示这个日志需要应用在所有下标<=idx的数组元素上
- 0/1 表示 inc/mul
- inc/m 表示具体的value
- ops数组是按照idx排序的,所以可以进行二分搜索
class Fancy: def __init__(self): self.ops = [] self.array = [] def append(self, val: int) -> None: self.array.append(val) def addAll(self, inc: int) -> None: idx = len(self.array) - 1 self.ops.append((idx, 0, inc)) def multAll(self, m: int) -> None: idx = len(self.array) - 1 self.ops.append((idx, 1, m)) def getIndex(self, idx: int) -> int: if idx >= len(self.array): return -1 MOD = 10 ** 9 + 7 s, e = 0, len(self.ops) - 1 while s <= e: m = (s + e) // 2 if self.ops[m][0] >= idx: e = m - 1 else: s = m + 1 # starts with s val = self.array[idx] for idx, op, v in self.ops[s:]: if op == 0: val += v else: val *= v return val % MOD
很明显这个问题出在,如果在某个时候我们想看idx=0的元素的时候,那是要回放所有的日志。如果按照系统设计的方式,是要做snapshot来减少日志回访量的,不过这里不现实。
这里如何做合并呢?也就是如何将这些计算组合起来呢?考虑 (x+a)*b = x*b+a*b. 可以想到 add(a), mul(b), 可以合并成为一个操作就是 x*b + a*b. 如果之后 mul(c)的话,那么就是 x*b*c + a*b*c. 依次类推。 最后 add/mul 合并在一起,就是就可以组合成为一个 f(x)=x*a+b.
虽然上面说到snapshot不现实,但是我们可以使用类似线段树的组织方式,将一些日志做合并,减少日志的应用数量。但是我觉得可能代码会比较多。
这里说一个更简单的办法,就是前面说到日志可以合并应用,但是其实也也是可以取消的(计算组合性)。
- 比如要执行 add(a), mul(b), add(c), mul(d) 这些操作
- 先执行 add(a), mul(b), 那么f(x) = x*b + a*b (系数分别是 b, a*b)
- 在执行 add(c), mul(d), 那么f(x) = x*b*d + (a*b+c) *d (系数分别是 b*d, (a*b+c)*d)
如果我们这时候想取消 add(a), mul(b), 应该怎么办呢?
- 我们应该使用什么乘数: b*d/d
- 我们应该使用什么加数:(a*b+c)*d / d - c
讲清楚这件事情好像有点困难,不过思路大体是清楚的。按照这个思路实现的话,就是下面这样的代码。注意下面这个代码使用了python的大数功能,想要不使用大数功能就需要解决除法取模的问题。
MOD = 10 ** 9 + 7 class Fancy: def __init__(self): self.array = [] self.ops = [] self.ops.append((-1, 0, 1)) def append(self, val: int) -> None: self.array.append(val) def addAll(self, inc: int) -> None: idx = len(self.array) - 1 _, add, mul = self.ops[-1] self.ops.append((idx, add + inc, mul)) def multAll(self, m: int) -> None: idx = len(self.array) - 1 _, add, mul = self.ops[-1] self.ops.append((idx, add * m, mul * m)) def getIndex(self, idx: int) -> int: if idx >= len(self.array): return -1 s, e = 0, len(self.ops) - 1 while s <= e: m = (s + e) // 2 if self.ops[m][0] >= idx: e = m - 1 else: s = m + 1 # print(self.ops) # apply latest op # and cancel op self.ops[e] _, add1, mul1 = self.ops[-1] _, add2, mul2 = self.ops[e] val = self.array[idx] mul = mul1 // mul2 add = add1 - add2 * mul ans = val * mul + add return ans % MOD
除法取模的问题需要使用到 欧拉和费马定理, 简单地说就是 x / p % MOD = x * POW(p, MOD-2) % MOD. 所以不适用大数除法的版本如下。
MOD = 10 ** 9 + 7 def POW(a, b): ans = 1 a = a % MOD while b: if b & 0x1: ans = ans * a ans = ans % MOD b = b >> 1 a = (a * a) % MOD return ans class Fancy: def __init__(self): self.array = [] self.ops = [] self.ops.append((-1, 0, 1, 1)) def append(self, val: int) -> None: self.array.append(val) def addAll(self, inc: int) -> None: idx = len(self.array) - 1 _, add, mul, _ = self.ops[-1] self.ops.append((idx, add + inc, mul, POW(mul, MOD - 2))) def multAll(self, m: int) -> None: idx = len(self.array) - 1 _, add, mul, _ = self.ops[-1] self.ops.append((idx, add * m, (mul * m) % MOD, POW(mul * m, MOD - 2))) def getIndex(self, idx: int) -> int: if idx >= len(self.array): return -1 s, e = 0, len(self.ops) - 1 while s <= e: m = (s + e) // 2 if self.ops[m][0] >= idx: e = m - 1 else: s = m + 1 # print(self.ops) # apply latest op # and cancel op self.ops[e] _, add1, mul1, div1 = self.ops[-1] _, add2, mul2, div2 = self.ops[e] val = self.array[idx] # mul = mul1 // mul2 # add = add1 - add2 * mul # ans = val * mul + add mul = (mul1 * div2) % MOD add = add1 - add2 * mul ans = val * mul + add while ans < 0: ans += MOD return ans % MOD