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;
}
posted @ 2024-04-07 16:54  2huk  阅读(105)  评论(0)    收藏  举报
2048 Game
Score
0
Best
0