AzusidNya 17分鐘前:
多項式快速冪 \(n\log n\) 跑 \(1e5\) 跑了 \(4\) 秒,樂
- 刪除
P5488 差分與字首和
給定一個長為 \(n\) 的序列 \(a\),求出其 \(k\) 階差分或字首和。
結果的每一項都需要對 \(1004535809\) 取模。\(1 \le n \le 10^5\)
\(0 \le a_i \le 10^9\)
\(1\le k \le 10^{2333}, k \not \equiv 0 \pmod{1004535809}\)
看過《具體數學》的話,很容易看出字首和就是其生成函式卷積上 \((1 - x) ^ {-1}\),差分就是捲上 \((1 - x)\)。
當然這裡也推一下吧。
定義 \(A = \sum _{i} a_ix^i\),\(B = \sum _i x^i\)。
而
所以很進行一次字首和就是捲上 \((1 - x) ^ {-1}\),而差分和字首和是逆運算,所以只用捲上 \((1 - x)\) 就行了。
\(n\) 次字首和就是捲上 \((1 - x) ^ {-k}\),差分就是捲上 \((1 - x)^k\)
之前做過多項式快速冪,知道這裡這麼大的 \(k\) 是可以直接對模數取模的。
多項式快速冪和多項式求逆拍上去,做完了(誤
好吧 AzusidNya 推到這一步這麼做了,然後就有了上面第一句話。
雖然時間複雜度是 \(O(n \log n)\) 的,但是常數巨大。考慮最佳化,能卡常過去的可以無視下面的內容。
我們感覺 \((1 - x)^k\) 是很好看的,考慮不快速冪求出這個多項式的係數。
透過二項式定理把它展開,得到:
這個式子可以遞推求出來。
具體來說:
然後我們可以把係數搞出來。
接下來求字首和完全可以套一個多項式求逆。這樣已經不會 TLE 了。
但是其實字首和也能一樣地用遞推求出所有係數。
找回上面的式子:
這裡用下降冪表示二項式係數:
把 \(-k\) 代進去。
這玩意也是可以遞推的,和上面的推導方法差不多,這裡懶得再推一遍了。
這兩個多項式的係數遞推完後直接卷積就行了,問題變成了 【模板】多項式乘法。
程式碼刪除了 namespace Poly 部分,因為 \(1000\) 個人有 \(1000\) 個多項式板子。
#include<iostream>
#include<fstream>
#include<algorithm>
#include<vector>
#include<string>
#define int long long
using namespace std;
namespace azus{
using namespace Poly;
int n, opt;
int k;
string sk;
poly a;
int main(){
cin >> n >> sk >> opt;
for(int i = 0; i < sk.size(); i ++){
k = 1ll * k * 10 % P;
k += sk[i] - '0';
k %= P;
}
// cout << k << "\n";
for(int i = 0, x; i < n; i ++){
cin >> x;
a.push_back(x);
}
poly d;
d.resize(n);
d[0] = 1;
if(opt == 0)
for(int i = 1; i < n; i ++)
d[i] = (d[i - 1] * ((P + k + i - 1) % P) % P) * Ksm(i, P - 2) % P;
else{
for(int i = 1; i < n; i ++)
d[i] = ((P - d[i - 1]) * ((P + k - i + 1) % P) % P) * Ksm(i, P - 2) % P;
}
// polyOutput(d);
a = convolution(a, d);
a.resize(n);
polyOutput(a);
return 0;
}
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T = 1;
while(T --) azus::main();
return 0;
}