题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6589
题目大意:初始有一个长为 n 的序列 a,有三类操作,每一类操作的定义(k为操作类型,
1
<
=
k
<
=
3
1 <= k <= 3
1<=k<=3 ):对于所有的 i,,每次操作之后会把 a 数组替换成 b,输入会给出一个长为 m 的操作序列。
最后求 ans = (1 * a[1]) ^ (2 * a[2) ^ (3 * a[3])… (^为异或运算符)
题解:贴一篇讲得比较详细的大佬的题解
https://www.cnblogs.com/xusirui/p/11229450.html
这题一开始连题目都没看懂,完全是跟着题解补的。
观察那个操作,其实就是求间隔 k 的前缀和,令b[i] = a 数组从 1 到 i 间隔 k - 1 的前缀和,然后用b数组替换掉a数组。看不懂这题就没了。
然后有一个操作序列,本来是让你按着这个序列的顺序从左往右依次做这种操作,对最后得到的序列求答案。然后这题有一个性质,就是操作序列的操作顺序并不影响最后的结果,如果没有发现这个性质,这题也没了,官方题解的证明也没看懂,不过可以通过暴力的方式发现这个性质,暴力的话每一次操作都要求前缀和,然后变换序列,复杂度为o(n * m),然后良心样例给的全是一样的操作,所以你写出暴力之后得自己出数据,这个好解决。
有了这个性质之后,既然可以交换操作顺序,那么一样的操作放一起做,所以开一个桶,记录每种操作的次数,一次性把某种操作做完,然后做下一种。
对于某一类操作,举例来说,设 k = 1,操作变成一个间隔为1的最朴素的前缀和,假设这种操作有 c 次,就是对 a 序列做 c 次前缀和。
初始 a 数组:
a
[
1
]
,
a
[
2
]
,
a
[
3
]
,
a
[
4
]
.
.
.
a[1],a[2],a[3],a[4]...
a[1],a[2],a[3],a[4]...
做一次前缀和:
a
[
1
]
,
a
[
1
]
+
a
[
2
]
,
a
[
1
]
+
a
[
2
]
+
a
[
3
]
,
a
[
1
]
+
a
[
2
]
+
a
[
3
]
+
a
[
4
]
a[1],a[1] + a[2],a[1] + a[2] + a[3],a[1] + a[2] + a[3] + a[4]
a[1],a[1]+a[2],a[1]+a[2]+a[3],a[1]+a[2]+a[3]+a[4]
做两次前缀和:
a
[
1
]
,
2
∗
a
[
1
]
+
a
[
2
]
,
3
∗
a
[
1
]
+
2
∗
a
[
2
]
+
a
[
3
]
,
4
∗
a
[
1
]
+
3
∗
a
[
2
]
+
2
∗
a
[
3
]
+
a
[
1
]
a[1],2 * a[1] + a[2],3 * a[1] + 2 * a[2] + a[3],4 * a[1] + 3 * a[2] + 2 * a[3] + a[1]
a[1],2∗a[1]+a[2],3∗a[1]+2∗a[2]+a[3],4∗a[1]+3∗a[2]+2∗a[3]+a[1]
做三次前缀和:
a
[
1
]
,
3
∗
a
[
1
]
+
a
[
2
]
,
6
∗
a
[
1
]
+
3
∗
a
[
2
]
+
a
[
3
]
,
10
∗
a
[
1
]
+
6
∗
a
[
2
]
+
3
∗
a
[
3
]
+
a
[
4
]
a[1],3 * a[1] + a[2],6 * a[1] + 3 * a[2] + a[3],10 * a[1] + 6 * a[2] + 3 * a[3] + a[4]
a[1],3∗a[1]+a[2],6∗a[1]+3∗a[2]+a[3],10∗a[1]+6∗a[2]+3∗a[3]+a[4]
…
观察一下系数,尤其是到第三次前缀和之后可以发现跟组合数有点关系,但一眼不好确定,没关系,画出杨辉三角。
会发现,系数就是那条斜线,而且是卷积形式,而且第几次前缀和就是第几条斜线。
用一个数组第 c 条斜线弄出来,快速求一下卷积,也就能快速搞出 c 次操作之后的 a 数组,搞三次即可。
对于 k = 2 和 k = 3的情况,需要将数组拆开,然后分开求卷积。标程用了更好的写法:将斜线数组间隔开,例如 C 表示那条斜线,让 C 数组下标每间隔 k - 1 取一个值,然后直接和原数组求卷积。
例如 K = 2:C[0],0,C[1],0,C[2]… ,(C[0],C[1],C[2] 表示斜线的值)
k = 3:C[0],0,0,C[1],0,0,C[2],0,0,C[3]
由于要取模,FFT是行不通了,不得不采用NTT(快速数论变换)。听起来好像需要学新东西,实际上NTT只是用来求取模的卷积,卷积形式没有变。
这里再讲一下NTT,NTT和FFT过程完全一样。FFT用的是 n次单位根 + 迭代蝴蝶操作将系数表达式转成点值表达式。NTT用的是原根,所谓原根:
g
i
m
o
d
  
p
g^i \mod p
gimodp ,
g
∈
[
2
,
p
−
1
]
g \in [2,p - 1]
g∈[2,p−1] 对于
i
∈
[
0
,
p
−
1
]
i \in [0,p - 1]
i∈[0,p−1],取模结果两两不相同,那么称 g 是 p的原根。关于原根的性质,可以百度查资料,还有一种定义是 对于
g
i
m
o
d
  
p
=
1
g^i \mod p = 1
gimodp=1 成立的 最小的 i (称为 g mod p 的阶),若 i =
ϕ
(
p
)
\phi(p)
ϕ(p),那么 g 是 p的原根,稍加思考就能发现这两种定义是一样。
NTT就用
g
p
−
1
n
m
o
d
  
p
g^{\frac{p - 1}{n}}\mod p
gnp−1modp,代替FFT中的
e
2
∏
i
n
e^{\frac{2\prod i}{n}}
en2∏i,原根具有和单位复数根相似的性质,它们的蝴蝶操作是一样的,逆变换也是一样的,因为 n 必须处理成 2 的幂,n 必须是 p -1 的因子,所以 p 必须是费马素数,费马素数是形如:
c
∗
2
k
+
1
c * 2 ^ k + 1
c∗2k+1的素数,998244353就是一个费马素数,且它的原根为3。
除此之外求卷积方式完全一样,直接套模板即可。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 10;
const int mod = 998244353; //原根是3
typedef long long ll;
int t,n,m,x;
ll a[maxn],c[maxn],d[maxn],b[maxn],tmp[maxn],num[5];
ll fact[maxn],ifact[maxn];
ll fpow(ll a,ll b) {
ll r = 1;
while(b) {
if(b & 1) r = r * a % mod;
a = a * a % mod;
b >>= 1;
}
return r % mod;
}
void change(ll t[],int len) {
for(int i = 1, j = len / 2; i < len - 1; i++) {
if(i < j) swap(t[i],t[j]);
int k = len / 2;
while(j >= k) {
j -= k;
k /= 2;
}
if(j < k) j += k;
}
}
void NTT(ll t[],int len,int type) {
change(t,len);
for(int s = 2; s <= len; s <<= 1) {
ll wn = fpow(3,(mod - 1) / s);
if(type == -1) wn = fpow(wn,mod - 2);
for(int j = 0; j < len; j += s) {
ll w = 1;
for(int k = 0; k < s / 2; k++) {
ll u = t[j + k],v = t[j + k + s / 2] * w % mod;
t[j + k] = (u + v) % mod;
t[j + k + s / 2] = (u - v + mod) % mod;
w = w * wn % mod;
}
}
}
if(type == -1) {
ll inv = fpow(len,mod - 2);
for(int i = 0; i < len; i++)
t[i] = t[i] * inv % mod;
}
}
void solve(ll x[],ll y[],int n) {
int len = 1;
while(len <= 2 * n) len <<= 1;
for(int i = n; i < len; i++)
x[i] = y[i] = 0;
NTT(x,len,1);
NTT(y,len,1);
for(int i = 0; i < len; i++)
tmp[i] = x[i] * y[i] % mod;
NTT(tmp,len,-1);
for(int i = 0; i < n; i++) a[i] = tmp[i];
}
ll C(int n,int m) {
return m<=n? fact[n]*ifact[m]%mod*ifact[n-m]%mod:0;
}
int main() {
fact[0]=1;
for(int i=1;i<=maxn-1;++i)
fact[i]=(long long)fact[i-1]*i%mod;
ifact[maxn-1]=fpow(fact[maxn-1],mod-2);
for(int i=maxn-2;i>=0;--i)
ifact[i]=(long long)ifact[i+1]*(i+1)%mod;
scanf("%d",&t);
while(t--) {
memset(num,0,sizeof num);
scanf("%d%d",&n,&m);
for(int i = 0; i < n; i++)
scanf("%lld",&a[i]);
for(int i = 1; i <= m; i++) {
scanf("%d",&x);
num[x]++;
}
for(int j = 1; j <= 3; j++) {
if(!num[j]) continue;
for(int k = 0; k < n; k++) d[k] = 0;
for(int k = 0; k * j < n; k++)
d[k * j] = C(num[j] + k - 1,k);
for(int k = 0; k < n; k++) b[k] = a[k];
solve(d,b,n);
}
ll res = 0;
for(int i = 0; i < n; i++)
res = res ^ ((i + 1) * a[i]);
printf("%lld\n",res);
}
return 0;
}