AtCoder Beginner Contest 348
\(\color{red}(1)\) E - Minimize Sum of Distances
- 给定一颗 \(n\) 个节点的树和长度为 \(n\) 的序列 \(c_1 \dots c_n\)。设 \(f(x) = \sum\limits_{i=1}^n (c_i \times \operatorname{dis}_{i, x})\),求 \(\min\limits_{1 \le v \le n} f(v)\)。其中 \(\operatorname{dis}_{a, b}\) 表示树上 \(a, b\) 的简单路径上边的数量。
- \(n \le 10^5\),\(c_i \le 10^9\)。
令 \(1\) 为根。设 \(T(x)\) 表示以 \(x\) 为根的子树。
显然可以轻易地求出 \(f(1)\)。思考当 \(v\) 的值发生变化时,\(f(v)\) 的变化。
如果 \(v\) 从本身移动到了它的某个儿子,那么 \(v\) 到 \(T(v)\) 中的每个点的距离都减少了 \(1\),\(v\) 到 \(T(v)\) 外的每个点的距离都增加了 \(1\)。
由于在题目中 \(f\) 值计算时,距离前面有一个系数 \(c_i\),那么 \(v\) 到 \(T(v)\) 中的每个点的距离减少 \(1\) 等价于 \(f(v)\) 的值减少了 \(\sum_{u \in T(v)} c_u\),\(v\) 到 \(T(v)\) 外的每个点的距离增加 \(1\) 等价于 \(f(v)\) 的值增加了 \(\sum_{u \not \in T(v)} c_u\)。
然后像这样类似 DP 地转移即可。
$\color{blue}\text{Code}$
int n, u, v, w[N];
int h[N], e[N], ne[N], idx;
int res, sum;
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
int dep[N], S;
int F[N];
void dfs(int u, int f) {
dep[u] = dep[f] + 1;
sum += (dep[u] - 1) * w[u];
F[u] = w[u];
for (int i = h[u]; ~i; i = ne[i]) {
int v = e[i];
if (v == f) continue;
dfs(v, u);
F[u] += F[v];
}
return;
}
void dfs2(int u, int f) {
for (int i = h[u]; ~i; i = ne[i]) {
int v = e[i];
if (v == f) continue;
sum -= F[v];
sum += S - F[v];
res = min(res, sum);
dfs2(v, u);
sum -= S - F[v];
sum += F[v];
}
return;
}
void solve() {
memset(h, -1, sizeof h);
fin >> n;
for (int i = 1; i < n; ++ i ) {
fin >> u >> v;
add(u, v), add(v, u);
}
for (int i = 1; i <= n; ++ i ) fin >> w[i], S += w[i];
dfs(1, 0);
res = sum;
dfs2(1, 0);
fout << res;
}
\(\color{red}(2)\) F - Oddly Similar
- 有 \(n\) 个长度为 \(m\) 的序列 \(A_i\)。如果两个序列 \(X, Y\) 满足有奇数个 \(i\) 满足 \(X_i = Y_i\),那么 \(X, Y\) 就是相似的。求有多少对 \((i,j )\) 满足 \(1 \le i < j \le n\) 且 \(A_i\) 和 \(A_j\) 是相似的。
- \(1 \le n, m \le 2000\),\(1 \le a_{i, j} \le 999\)。
直接暴力是 \(\mathcal O(n^2m)\)。听说卡常能过?
不难发现这个复杂度除以 \(\omega = 64\) 是可以接受的,也就是提示我们用 bitset。
bitset 最后再用。先考虑怎么将数据用 bool 数组存储。
定义 bool 型的 \(cnt_{j, k, i}\),若 \(A_{i, j} = k\) 则 \(cnt_{j, k, i} = 1\),否则 \(cnt_{j, k, i} = 0\)。这里 \(i\) 指行,\(j\) 指列,\(k\) 指值。
考虑计算行 \(i\) 的答案。更具体地,我们需要计算哪些行与 \(i\) 是相似的。进一步地,我们定义 bool 型的 \(f_j\) 表示 \(A_i\) 和 \(A_j\) 是否是相似的。开始计算前,所有 \(f_j = 0\)。
那么类似于暴力,我们需要枚举列 \(j\)。此时,显然我们已经明确了 \(cnt_{j, a_{i, j}, i} = 1\)。若我们求出了对于 \((i_1, i_2, \dots, i_p)\) 都满足 \(cnt_{j, a_{i, j}, i_p} = 1\),也就是这些行的第 \(j\) 列都是 \(a_{i, j}\),也就是这些行与第 \(i\) 行的第 \(j\) 列都相同,那么这就意味着,\(f_{i_1}, f_{i_2}, \dots, f_{i_p}\) 的值都会发生反转。
代码是这样的:
$\color{blue}\text{Code}$
const int N = 2001, M = 1000;
bool cnt[N][M][N], f[N];
for (int i = 1; i <= n; ++ i )
for (int j = 1; j <= m; ++ j )
cnt[j][a[i][j]][i] = 1;
for (int i = 1; i <= n; ++ i ) {
for (int j = 1; j <= n; ++ j ) f[j] = 0;
for (int j = 1; j <= m; ++ j )
for (int k = 1; k <= n; ++ k ) {
f[k] ^= cnt[j][a[i][j]][k];
// 原写法是:if (cnt[j][a[i][j]][k]) f[k] ^= 1;
}
f[i] = 0; // 显然自己和自己不能构成答案
for (int j = 1; j <= n; ++ j ) res += f[j];
}
printf("%d\n", res / 2);
这样的复杂度还是 \(\mathcal O(n^2m)\) 的。但是我们就可以将其中的 bool 数组换成 bitset 加速了。具体的,可以发现对于每一个 \(f_{1 \sim n}\) 和 \(cnt_{j, k, 1 \sim n}\) 都是一维的 bool 数组。我们可以将这些一维数组改为 bitset。
那么中间 for (int k = 1; k <= n; ++ k ) f[k] ^= cnt[j][a[i][j]][k];
就可以改为 f ^= cnt[j][a[i][j]];
了。总复杂度降低为 \(\mathcal O\left( \frac {n^2m}{\omega} \right)\)。
$\color{blue}\text{Code}$
#include <bits/stdc++.h>
using namespace std;
const int N = 2001, M = 1000;
int n, m, a[N][N], res;
bitset<N> cnt[N][M], f;
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++ i )
for (int j = 1; j <= m; ++ j ) {
scanf("%d", &a[i][j]);
cnt[j][a[i][j]][i] = 1;
}
for (int i = 1; i <= n; ++ i ) {
f.reset();
for (int j = 1; j <= m; ++ j ) f ^= cnt[j][a[i][j]];
f[i] = 0; // 显然自己和自己不能构成答案
res += f.count();
}
printf("%d\n", res / 2);
return 0;
}