E 小苯的xor图
这道题要求我们计算一个关于图中所有长度为2的路径的特定权值总和。一个长度为2的路径由三个点 u,v,w 构成,其中边 (u,v) 和 (v,w) 存在。
形式化地说,我们需要计算的公式是:
其中 N(v) 表示节点 v 的邻居集合。u<w 是为了确保每对邻居只被计算一次。
思路:按位贡献
一个数 X 的值可以表示为 sum_b=0^k*2^b
就是cdot(X_b),其中 X_b 是 X 在二进制表示下的第 b 位。利用加法的分配律,总和 S 就可以转化为计算每一位贡献的总和:
这里的 (a_i)_b 指的是 a_i 的二进制第 b 位。
现在,问题转化为:对于每一个二进制位 b,计算有多少个路径 (u,v,w) 使得其权值的第 b 位为1。
具体代码:
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int M = 200005;
const int MOD = 998244353;
vector<int> j[M];
int n,m,a[M];
void solve() {
cin >> n >> m;
for (int i = 1; i <= n; ++i) cin >> a[i];
for (int i = 0; i < m; ++i) {
int u, v;
cin >> u >> v;
j[u].push_back(v);
j[v].push_back(u);
}
ll t = 0;
// 按位枚举
for (int b = 0; b <= 30; ++b) {
ll cnt = 0;
// 枚举中心点v
for (int v = 1; v <= n; ++v) {
ll c1 = 0,c2 = 0;
// 统计邻居中第b位为0和1的数量
for (int ne : j[v]) {
if ((a[ne] >> b) & 1) {
c2++;
} else {
c1++;
}
}
ll p = 0;
// 根据中心点v的第b位计算贡献
if ((a[v] >> b) & 1) {
// (a_v)_b = 1, 需要 (a_u)_b == (a_w)_b
p = (c1 * (c1 - 1) / 2) + (c2 * (c2 - 1) / 2);
} else {
// (a_v)_b = 0, 需要 (a_u)_b != (a_w)_b
p = c1 * c2;
}
cnt += p;
}
// 累加当前位的总贡献
ll p0 = (1LL << b) % MOD,b0 = (cnt % MOD * p0) % MOD;
t = (t + b0) % MOD;
}
cout << t << endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(NULL);
solve();
return 0;
}
F 小苯的前缀gcd构造
思路:动态规划+bitset
我们定义 dp[i][g] 为一个 bitset。如果 dp[i][g] 的第 s 位为1,则表示存在一个长度为 i、以 g 结尾、且满足整除条件的序列,其元素总和为 s。
在 bitset 上,这相当于将 dp[i−1][k] 中所有为1的位向左移动 g 位。我们将所有合法的 k(即 g 的倍数)所对应的 dp[i−1][k] 左移 g 位后的结果进行按位或运算,就得到了 dp[i][g]
状态转移方程:
基础情况
当 i=1 时,序列只有一个元素 g。其和就是 g。所以,对于所有 g in[1,m],我们在 dp[1][g] 的第 g 位上置1。
具体解法
-
完成DP表格的计算后,我们检查是否存在解。我们遍历所有可能的结尾元素 g in[1,m],查看 dp[n][g] 的第 x 位是否为1。如果存在,则说明有解。我们记录下第一个找到的 g 作为序列的最后一个元素 a_n。
-
反向推导整个序列,已知 a_n,当前的总和为 x。那么前 n−1 个元素的和必须是 x−a_n,我们需要找到 a_n−1,它必须是 a_n 的倍数,并且 dp[n−1][a_n−1] 的第 x−a_n 位必须为1,我们从小到大遍历 a_n 的倍数来寻找合适的 a_n−1,以此类推,直到找到 a_1 为止。
具体代码:
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int M = 50;
const int N = M*M;
using dd = vector<vector<bitset<N + 1>>>;
void solve() {
int n, m,x;
cin >> n >> m >> x;
if (x < n) { // 最小和是n个1,如果x<n则无解
cout << -1 << endl;
return;
}
dd dp(n + 1, vector<bitset<N + 1>>(m + 1));
// 基础情况: i = 1
for (int g = 1; g <= m; ++g) {
if (g <= x) {
dp[1][g][g] = 1;
}
}
for (int i = 2; i <= n; ++i) {
for (int g = 1; g <= m; ++g) {
// 遍历所有可能的 a_{i-1} = k,其中 k 是 g 的倍数
for (int k = g; k <= m; k += g) {
// 从 (i-1, k) 状态转移到 (i, g)
dp[i][g] |= (dp[i-1][k] << g);
}
}
}
int l = -1;
for (int g = 1; g <= m; ++g) {
if (x <= N && dp[n][g][x]) {
l = g; // 找到了一个可行的结尾 a_n
break;
}
}
if (l == -1) {
cout << -1 << endl;
} else {
vector<int> a(n);
int c = l; // 当前要找的数
int r = x; // 剩余的和
for (int i = n; i >= 1; --i) {
a[i - 1] = c;
r -= c;
if (i > 1) {
// 寻找 a_{i-1} = p且p 必须是 c 的倍数
for (int p = c; p <= m; p += c) {
if (r >= 0 && r <= N && dp[i - 1][p][r]) {
c = p;
break;
}
}
}
}
for (int i = 0; i < n; ++i) {
cout << a[i] << (i == n - 1 ? "" : " ");
}
cout << endl;
}
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
int t;
cin >> t;
while (t--) solve();
return 0;
}