你这里不是“必须先乘后加”,而是:
你选择的状态定义决定了
getIndex里应该怎么算。
题解里定义的是:
既然你采用了这个定义,那么 getIndex 就必须按这个式子算,也就是:
| C++ | |
|---|---|
而不能写成:
| C++ | |
|---|---|
因为这两个式子一般不相等。
1. 为什么 (x + add) * mul 不对?¶
因为展开后:
而题解维护的是:
注意最后一项一个是 add * mul,一个是 add,差很多。
2. 题解里的 mul 和 add 到底表示什么?¶
题解实际上是在维护一个仿射变换:
意思是:
vec[i] 里存的不是当前真实值,而是一个“原始值” x,真正显示出来的值是:
初始时:
所以:
mul = 1add = 0
3. 为什么 multAll 时要同时更新 mul 和 add?¶
假设当前真实值是:
现在执行一次 multAll(m),所有数都乘 m,那么新真实值应变成:
展开:
所以更新规则就是:
这正是题解里的写法。
4. 为什么 addAll 只改 add?¶
当前真实值:
执行 addAll(inc) 后:
所以只需要:
| C++ | |
|---|---|
5. 为什么 append 要写成 (val - add) / mul?¶
因为你存进 vec 的不是最终值,而是原始值 x,并且你约定:
现在要追加一个新值 val,要求这个新元素此刻的真实值就是 val,那么必须满足:
解出来:
在模意义下就是:
所以应写成:
| C++ | |
|---|---|
而你现在写的是:
| C++ | |
|---|---|
这相当于:
这是错误的,因为:
一般不成立。
6. 你代码里 getIndex 还有一个明显 bug¶
你写的是:
这里有两个问题。
问题 1:顺序错了¶
应该是:
| C++ | |
|---|---|
而不是先加后乘。
问题 2:最后写成了 * mod¶
你这里:
| C++ | |
|---|---|
应该是:
| C++ | |
|---|---|
否则结果会直接乘上 1e9+7,完全错掉。
7. 用一个具体例子说明为什么必须和定义一致¶
假设:
真实序列变化:
- 初始:
[2] addAll(3)后:[5]multAll(2)后:[10]
按题解的定义¶
维护:
append (2)¶
当时 mul=1, add=0
存入 vec=[2]
addAll (3)¶
更新:
add=3mul=1
multAll (2)¶
更新:
mul=2add=6
所以最终:
完全正确。
如果你在 getIndex 写成先加后乘¶
你会算:
显然错了。
因为此时 add=6 的含义已经不是“先加 6 再乘”,而是题解状态下最终表达式中的那个“末尾加项”。
8. 那能不能定义成“先加后乘”?¶
可以。
你完全可以定义:
那么 getIndex 就可以先加后乘。
但代价是:
addAllmultAllappend
这些操作的更新公式都会变掉, 而且通常没有题解那种写法自然。
比如若定义:
那么:
multAll(m)¶
[ ((x+a)\cdot b)\cdot m=(x+a)\cdot (bm) ] 所以:
b *= m
addAll(inc)¶
想要:
但你又想保持成:
则必须有:
所以:
这就意味着每次 addAll 都要乘逆元,不如题解直接维护 x*mul+add 方便。
9. 你的代码应该怎么改?¶
改成这样:
10. 总结一句¶
你不是“必须先乘后加”,而是:
因为你当前维护的是
真实值 = vec[i] * mul + add,所以getIndex必须按这个公式算。
如果你想先加后乘,也可以,但你必须从头把整个状态定义改成:
并重新推导:
appendaddAllmultAll
的更新规则。
不能一边用题解的状态维护方式,一边在 getIndex 里改成另一种公式。
如果你愿意,我可以继续帮你把这道题的“状态定义为什么这样选最方便”再用更直观的方式讲一遍。