bzoj1919: [Ctsc2010]性能优化
权限题贴个题面。
luogu上也有。
分析
循环卷积裸题。
由于
w
n
i
=
w
n
i
m
o
d
  
n
w_n^i= w_n^{i \mod n}
wni=wnimodn
所以说
w
n
i
w
n
j
=
w
n
(
i
+
j
)
m
o
d
  
n
w_n^iw_n^j=w_n^{(i+j)\mod n}
wniwnj=wn(i+j)modn
发现这刚好是循环卷积的形式。
所以说做长度为
n
n
n的
D
F
T
DFT
DFT之后再
I
D
F
T
IDFT
IDFT就好了。
重点在于这题的
n
n
n的长度不是
2
n
2^n
2n,模数也非常诡异。
所以要介绍Cooley–Tukey FFT algorithm
注意到模数比较小而且是质数,所以直接映射单位复根到模意义下的原根。
回忆快速数论变换的过程。
e
2
π
i
n
≡
g
P
−
1
n
e^{\frac{2\pi i}{n}}\equiv g^{\frac{P-1}{n}}
en2πi≡gnP−1
这个时候我们不这么搞,直接
e
2
π
i
n
≡
g
e^{\frac{2\pi i}{n}}\equiv g
en2πi≡g
带点值的时候直接把
g
k
g^k
gk带进去就好了。
然后最关键的问题来了,
n
n
n不是
2
n
2^n
2n
但是题目给了一个条件
n
=
2
k
1
3
k
2
5
k
3
7
k
4
n=2^{k_1}3^{k_2}5^{k_3}7^{k_4}
n=2k13k25k37k4
F
F
T
FFT
FFT原本是即将序列分成两半,然后合并。
现在就吧序列分成
k
=
2
,
3
,
5
,
7
k=2,3,5,7
k=2,3,5,7块,然后合并。
比较麻烦的事情就来了——式子得重推。
对于一次分治,假设当前是分成
p
p
p块,当前总长度为
L
L
L,那么分治之后每块的大小就是
d
=
L
p
d=\frac{L}{p}
d=pL。
A
(
g
k
)
=
a
0
+
a
1
g
k
+
a
2
g
2
k
⋯
a
L
−
1
g
k
(
L
−
1
)
A(g^k)=a_0+a_1g^k+a_2g^{2k}\cdots a_{L-1}g^{k(L-1)}
A(gk)=a0+a1gk+a2g2k⋯aL−1gk(L−1)
为了方便,接下来的式子中令
X
=
g
k
X=g^k
X=gk
按照
m
o
d
  
p
\mod p
modp的剩余系分类
=
(
a
0
+
a
p
X
p
+
⋯
a
p
(
d
−
1
)
X
p
(
d
−
1
)
)
)
+
(
a
1
X
1
+
a
p
+
1
X
p
+
1
⋯
a
p
(
d
−
1
)
+
1
X
p
(
d
−
1
)
+
1
)
+
⋯
+
(
a
p
−
1
X
p
−
1
+
a
p
−
1
+
d
X
p
−
1
+
d
⋯
a
p
d
−
1
X
p
d
−
1
)
=(a_0+a_pX^p+\cdots a_{p(d-1)}X^{p(d-1))})+(a_1X^1+a_{p+1}X^{p+1}\cdots a_{p(d-1)+1}X^{p(d-1)+1})+\cdots +(a_{p-1}X^{p-1}+a_{p-1+d}X^{p-1+d}\cdots a_{pd-1}X^{pd-1})
=(a0+apXp+⋯ap(d−1)Xp(d−1)))+(a1X1+ap+1Xp+1⋯ap(d−1)+1Xp(d−1)+1)+⋯+(ap−1Xp−1+ap−1+dXp−1+d⋯apd−1Xpd−1)
=
∑
0
≤
b
<
p
∑
0
≤
a
<
d
a
a
p
+
b
X
a
p
+
b
=
∑
0
≤
b
<
p
X
b
∑
0
≤
a
<
d
a
a
p
+
b
X
a
p
=\sum_{0\le b< p}\sum_{0\le a<d}a_{ap+b}X^{ap+b}=\sum_{0\le b< p}X^b\sum_{0\le a<d}a_{ap+b}X^{ap}
=0≤b<p∑0≤a<d∑aap+bXap+b=0≤b<p∑Xb0≤a<d∑aap+bXap
考虑分治往下一层,这个时候稍微注意一下下一层的点值用
w
=
g
p
w=g^p
w=gp的次幂去带。
如果我们已经得到了分治之后的结果,也就是
A
0
(
w
m
)
=
a
0
+
a
p
w
m
+
⋯
a
p
(
d
−
1
)
w
m
(
d
−
1
)
=
∑
0
≤
a
<
d
a
a
p
w
m
a
A_0(w^m)=a_0+a_pw^m+\cdots a_{p(d-1)}w^{m(d-1)}=\sum_{0\le a < d}a_{ap}w^{ma}
A0(wm)=a0+apwm+⋯ap(d−1)wm(d−1)=∑0≤a<daapwma
A
b
(
w
m
)
=
∑
0
≤
a
<
d
a
a
p
+
b
w
m
a
A_b(w^m)=\sum_{0\le a < d}a_{ap+b}w^{ma}
Ab(wm)=∑0≤a<daap+bwma
m
∈
[
0
,
d
)
m\in[0,d)
m∈[0,d)
那么
A
(
g
k
)
=
∑
0
≤
b
<
p
g
k
b
∑
0
≤
a
<
d
a
a
p
+
b
g
a
k
p
=
∑
0
≤
b
<
p
g
k
b
A
b
(
g
k
p
)
=
∑
0
≤
b
<
p
g
k
b
A
b
(
w
k
)
A(g^k)=\sum_{0\le b< p}g^{kb}\sum_{0\le a<d}a_{ap+b}g^{akp}=\sum_{0\le b< p}g^{kb}A_b(g^{kp})=\sum_{0\le b< p}g^{kb}A_b(w^k)
A(gk)=0≤b<p∑gkb0≤a<d∑aap+bgakp=0≤b<p∑gkbAb(gkp)=0≤b<p∑gkbAb(wk)
一波推导,我们得到了一个式子。
A
(
g
k
)
=
∑
0
≤
b
<
p
g
k
b
A
b
(
w
k
)
A(g^k)=\sum_{0\le b< p}g^{kb}A_b(w^k)
A(gk)=0≤b<p∑gkbAb(wk)
美中不足的是
k
∈
[
0
,
L
)
k\in [0,L)
k∈[0,L)
这个时候令
k
=
u
d
+
v
k=ud+v
k=ud+v
那么
A
(
g
u
d
+
v
)
=
∑
0
≤
b
<
p
g
(
u
d
+
v
)
b
A
b
(
w
(
u
d
+
v
)
)
A(g^{ud+v})=\sum_{0\le b< p}g^{(ud+v)b}A_b(w^{(ud+v)})
A(gud+v)=0≤b<p∑g(ud+v)bAb(w(ud+v))
注意到
w
d
=
g
p
d
=
g
L
=
1
w^d=g^{pd}=g^L=1
wd=gpd=gL=1
于是就有
A
(
g
u
d
+
v
)
=
∑
0
≤
b
<
p
g
(
u
d
+
v
)
b
A
b
(
w
v
)
A(g^{ud+v})=\sum_{0\le b< p}g^{(ud+v)b}A_b(w^{v})
A(gud+v)=0≤b<p∑g(ud+v)bAb(wv)
搞定!
果然还是太菜了,网上的大佬似乎都是直接秒出这个式子,只有蒟蒻用markdown推了半天。。。
代码
#include<bits/stdc++.h>
const int N = 5e5 + 10;
int ri() {
char c = getchar(); int x = 0, f = 1; for(;c < '0' || c > '9'; c = getchar()) if(c == '-') f = -1;
for(;c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) - '0' + c; return x * f;
}
int a[N], b[N], w[N], R[N], pr[101], tot, P, G, n;
int add(int &a, int b) {return a += b, a >= P ? a - P : a;}
int mul(int a, int b) {return 1LL * a * b % P;}
int Pow(int x, int k) {
int r = 1;
for(;k; x = mul(x, x), k >>= 1)
if(k & 1)
r = mul(r, x);
return r;
}
bool Ck(int x, int t) {
for(int i = 1;i <= tot; ++i)
if(Pow(x, t / pr[i]) == 1)
return true;
return false;
}
void Findori() {
int x = P - 1;
for(int i = 2;i <= 7; ++i)
for(;!(x % i); pr[++tot] = i, x /= i) ;
G = 2;
for(;Ck(G, n);) ++G;
}
int Pos(int x, int s, int de, int len) {
if(de == tot + 1) return s;
int d = len / pr[de], r = x % pr[de];
return Pos(x / pr[de], s + d * r, de + 1, d);
}
void DFT(int *A) {
static int B[N];
for(int i = 0;i < n; ++i)
B[R[i]] = A[i];
int *cur = B, *nxt = A;
for(int d = 1, de = tot; de; d *= pr[de--], std::swap(cur, nxt)) {
int pd = d * pr[de], p = n / pd; //w(n,m)^p=w(n/p,m)
for(int st = 0; st < n; st += pd)
for(int a = 0; a < pd; a += d) // a * d
for(int b = 0;b < d; ++b) {
int &ps = nxt[st + a + b], o = (a + b) * p; ps = 0;
for(int r = 0; r < pr[de]; ++r)
ps = add(ps, mul(w[1LL * o * r % n], cur[st + r * d + b]));
}
}
if(cur != A)
memcpy(A, cur, sizeof(int) * n);
}
int main() {
n = ri(); int C = ri() % n; P = n + 1;
Findori();
for(int i = 0;i < n; ++i)
a[i] = ri() % P;
for(int i = 0;i < n; ++i)
b[i] = ri() % P;
w[0] = 1;
for(int i = 1;i < n; ++i)
w[i] = mul(w[i - 1], G);
for(int i = 1;i < n; ++i)
R[i] = Pos(i, 0, 1, n);
DFT(a); DFT(b);
for(int i = 0;i < n; ++i)
a[i] = mul(a[i], Pow(b[i], C));
DFT(a);
std::reverse(a + 1, a + n);
for(int i = 0;i < n; ++i)
printf("%d\n", mul(a[i], n)); // n * n = 1 (mod n + 1)
return 0;
}