题目大意
给定一个长度为 n 的数组 a,以及三个初始值为 0 数 P,Q,R。进行 n 次操作,第 i 次操作中,选择 P,Q,R 中的一个数,变成与 a[i] 的异或和,且每次操作后 P,Q,R 中至少有两个数相等。求出合法的可能操作且对 1e9 + 7 取模。
题目分析
设数组 p 为数组 a 的前缀异或和,则由异或的性质可知:p[i] = p[i-1]^a[i]。又因为, P,Q,R 的初始值为0且每次操作都将其中的一个数更新为其与 a[i] 的异或和,故进行了 i 次操作后,PQR = p[i]。又因为最少有两个值相等,根据异或的性质 a ^ a = 0 ,假设 Q 与 R 相同,则 P = p[i]。因为 P,Q,R 三者是等价的,可以相互转换,故可以得出结论:执行 i 次操作后 P,Q,R 中必然有一个值为 p[i] 。
用(P,Q,R)表示三者的值,设 P 的值为 p[i] , dp[i,x] 为执行 i 次操作后相同的两数为 x 的总可能数,讨论状态转移方程:
一.进行i次操作后,为 (p[i],p[i],p[i]) :
1.由(p[i-1],p[i],p[i])或(p[i],p[i-1],p[i])或(p[i],p[i],p[i-1])转移得到,对于每种可能只有一种固定的转移方式(即只能对不为 p[i] 的数操作),故 dp[i,p[i]] = dp[i - 1,p[i]]
二.进行 i 次操作后,为 (p[i],p[i-1],p[i-1]):
1.由(p[i-1],p[i-1],p[i-1])转移得到,有三种可能(只能对p[i-1]操作,p[i-1]有三个),故 dp[i,p[i - 1]] = dp[i - 1,p[i - 1]] * 3;
2.由(p[i],p[i],p[i-1])转移得到,有两种可能(只能对p[i]操作,p[i]有两个),故 dp[i,p[i - 1]] = dp[i - 1,p[i]] * 2;
三.进行i次操作后,为(p[i],x,x)(x为不为p[i-1]和p[i]的任意值):
1.由(p[i-1],x,x)转移得到,只有一种可能(只能对p[i-1]操作),故 dp[i,x] = dp[i - 1,x];
综上所述,第 i - 1 次状态转移到第 i 次状态的状态转移方程只与第 i - 1 次状态下相同两数的值有关,故可以用滚动数组优化。重新设 dp[i] 为相同两数为 i 的总可能数,则执行一次操作后的状态转移方程为:
dp[p[i - 1]] = dp[p[i - 1]] * 3 + dp[p[i]] * 2
同时注意到,数值很大,不能直接建立数组进行存储,可以使用map建立映射关系,降低空间复杂度。
最后,代码实现,欢迎各位大佬指教
#include
#include
using namespace std;
long long t, a[200010] = {}, n, ans = 0, mod = 1e9 + 7;
map<long long, long long>dp;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> t;
while (t--) {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
a[i] ^= a[i - 1];//维护前缀异或和
}
dp.clear();
dp[0] = 1;//初始情况下P,Q,R全为0,所以dp[0]的值为1
for (int i = 1; i <= n; i++) {
dp[a[i - 1]] = (dp[a[i - 1]] * 3 % mod + dp[a[i]] * 2 % mod) % mod;//dp[i]中的i为P,Q,R中相同两个数的值,dp[i]的值为可能性
}
ans = 0;
for (auto [u, v] : dp) {
ans = (ans + v) % mod;//答案为map中可能性的和
}
cout << ans << '\n';
}
}