T1:异或序列

给定长度为 n 的数组 A,求出 i=1Nj=iNAiAi+1Aj
多组测试数据

限制:

  • 1T10
  • 2N2×105
  • 0Ai109

参考难度:

普及+/提高

算法分析

20
由于 n200,直接使用三层循环,枚举 A 数组所有区间,然后循环求异或即可。

40
需要联系到异或运算的特性,如果求 AiAi+1Aj,可以变成 A1A2Ai1AiAjA1A2Ai1 取异或。设 Si=Si1Ai=A1A2Ai,那么区间 [i,j] 的异或和可以表示成 Si1Sj。只需 O(1) 时间就能求出区间的异或和,可以优化成 O(n2)

100

由于异或运算是按位操作,所以二进制的不同位不会互相影响,我们可以分成 30 位进行考虑,那么 A 数组可以看作 10 的数字。
如果数组只有 1 或者 0,一个区间的异或和只和区间中 1 的个数奇偶性有关系。如果有奇数个 1,答案就是 1;如果有偶数个 1,答案就是 0
可以使用上面的 S 数组方便计算,枚举二进制的第 k 位,再枚举区间的右端点 j,如果希望对答案有贡献,那么 Si1Sj 在二进制第 k 位必须是 1。如果 Sj 在第 k 位是 1,那么它对答案的贡献是 2k×S0Sj 中第 k 位是 0 的个数;如果 Sj 在第 k 位是 0,那么它对答案的贡献是 2k×S0Sj 中第 k 位是 1 的个数。
整体时间复杂度为 O(nlogA),其中 logA 最大为 30

代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using ull = unsigned long long;
int main() {
cin.tie(nullptr) -> sync_with_stdio(false);
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
vector<int> a(n);
rep(i, n) cin >> a[i];
vector<int> s(n+1);
rep(i, n) s[i+1] = s[i]^a[i];
ull ans = 0;
rep(j, 30) {
int c0 = 1, c1 = 0;
ull now = 0;
for (int i = 1; i <= n; ++i) {
if (s[i]>>j&1) now += c0, c1++;
else now += c1, c0++;
}
ans += now*(1<<j);
}
cout << ans << '\n';
}
return 0;
}

T3: 小猴摘桃

给定一颗树,求树上经过偶数个节点的路径数量。

限制:

  • n105

参考难度:

普及+/提高

算法分析

30
枚举起点 S,枚举终点 T,使用 DFS 求出起点到终点的距离,如果距离是奇数,说明经过的结点是偶数个,答案加 1

60
枚举起点 S,使用 BFS 求出 S 到所有点的距离,对于距离为奇数的点,说明经过的结点是偶数个,答案加上距离是奇数的点的数量。
也可以枚举起点 S 和终点 T,然后使用 lcaST 的距离。

100

树形dp

dp[v][0/1] 表示以 v 为根的子树中到 v 距离为偶数/奇数的点的个数

转移方程:

dp[v][0] += dp[u][1]
dp[v][1] += dp[u][0]+1

最后的答案就是 (dp[1][0]+1)×dp[1][1]

代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using ll = long long;
int main() {
int n;
cin >> n;
vector<vector<int>> to(n);
rep(i, n-1) {
int a, b;
cin >> a >> b;
--a; --b;
to[a].push_back(b);
to[b].push_back(a);
}
vector<vector<int>> dp(n, vector<int>(2));
auto dfs = [&](auto& f, int v, int p=-1) -> void {
for (int u : to[v]) {
if (u == p) continue;
f(f, u, v);
dp[v][0] += dp[u][1];
dp[v][1] += dp[u][0]+1;
}
};
dfs(dfs, 0);
ll ans = (ll)(dp[0][0]+1) * dp[0][1];
cout << ans << '\n';
return 0;
}