T1:异或序列
给定长度为 \(n\) 的数组 \(A\),求出 \(\displaystyle \sum_{i=1}^N\sum_{j=i}^N A_i \oplus A_{i+1} \cdots \oplus A_j\)
多组测试数据
限制:
- \(1 \leqslant T \leqslant 10\)
- \(2 \leqslant N \leqslant 2 \times 10^5\)
- \(0 \leqslant A_i \leqslant 10^9\)
参考难度:
普及+/提高
算法分析
\(20\) 分
由于 \(n \leqslant 200\),直接使用三层循环,枚举 \(A\) 数组所有区间,然后循环求异或即可。
\(40\) 分
需要联系到异或运算的特性,如果求 \(A_i \oplus A_{i+1} \oplus \cdots A_j\),可以变成 \(A_1 \oplus A_2 \oplus \cdots \oplus A_{i-1} \oplus A_i \oplus \cdots \oplus A_j\) 和 \(A_1 \oplus A_2 \oplus \cdots \oplus A_{i-1}\) 取异或。设 \(S_i = S_{i-1} \oplus A_i = A_1 \oplus A_2 \oplus \cdots \oplus A_i\),那么区间 \([i, j]\) 的异或和可以表示成 \(S_{i-1} \oplus S_j\)。只需 \(O(1)\) 时间就能求出区间的异或和,可以优化成 \(O(n^2)\) 。
\(100\) 分
由于异或运算是按位操作,所以二进制的不同位不会互相影响,我们可以分成 \(30\) 位进行考虑,那么 \(A\) 数组可以看作 \(1\) 或 \(0\) 的数字。
如果数组只有 \(1\) 或者 \(0\),一个区间的异或和只和区间中 \(1\) 的个数奇偶性有关系。如果有奇数个 \(1\),答案就是 \(1\);如果有偶数个 \(1\),答案就是 \(0\)。
可以使用上面的 \(S\) 数组方便计算,枚举二进制的第 \(k\) 位,再枚举区间的右端点 \(j\),如果希望对答案有贡献,那么 \(S_{i-1} \oplus S_j\) 在二进制第 \(k\) 位必须是 \(1\)。如果 \(S_j\) 在第 \(k\) 位是 \(1\),那么它对答案的贡献是 \(2^k \times S_0\) 到 \(S_j\) 中第 \(k\) 位是 \(0\) 的个数;如果 \(S_j\) 在第 \(k\) 位是 \(0\),那么它对答案的贡献是 \(2^k \times S_0\) 到 \(S_j\) 中第 \(k\) 位是 \(1\) 的个数。
整体时间复杂度为 \(O(n\log A)\),其中 \(\log A\) 最大为 \(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: 小猴摘桃
给定一颗树,求树上经过偶数个节点的路径数量。
限制:
- \(n \leqslant 10^5\)
参考难度:
普及+/提高
算法分析
\(30\) 分
枚举起点 \(S\),枚举终点 \(T\),使用 DFS
求出起点到终点的距离,如果距离是奇数,说明经过的结点是偶数个,答案加 \(1\) 。
\(60\) 分
枚举起点 \(S\),使用 BFS
求出 \(S\) 到所有点的距离,对于距离为奇数的点,说明经过的结点是偶数个,答案加上距离是奇数的点的数量。
也可以枚举起点 \(S\) 和终点 \(T\),然后使用 lca
求 \(S\) 到 \(T\) 的距离。
\(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) \times 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;
}