AtCoder Beginner Contest 348

(1) E - Minimize Sum of Distances

  • 给定一颗 n 个节点的树和长度为 n 的序列 c1cn。设 f(x)=i=1n(ci×disi,x),求 min1vnf(v)。其中 disa,b 表示树上 a,b 的简单路径上边的数量。
  • n105ci109

1 为根。设 T(x) 表示以 x 为根的子树。

显然可以轻易地求出 f(1)。思考当 v 的值发生变化时,f(v) 的变化。

如果 v 从本身移动到了它的某个儿子,那么 vT(v) 中的每个点的距离都减少了 1vT(v) 外的每个点的距离都增加了 1

由于在题目中 f 值计算时,距离前面有一个系数 ci,那么 vT(v) 中的每个点的距离减少 1 等价于 f(v) 的值减少了 uT(v)cuvT(v) 外的每个点的距离增加 1 等价于 f(v) 的值增加了 uT(v)cu

然后像这样类似 DP 地转移即可。

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;
}

(2) F - Oddly Similar

  • n 个长度为 m 的序列 Ai。如果两个序列 X,Y 满足有奇数个 i 满足 Xi=Yi,那么 X,Y 就是相似的。求有多少对 (i,j) 满足 1i<jnAiAj 是相似的。
  • 1n,m20001ai,j999

直接暴力是 O(n2m)。听说卡常能过?

不难发现这个复杂度除以 ω=64 是可以接受的,也就是提示我们用 bitset。

bitset 最后再用。先考虑怎么将数据用 bool 数组存储。

定义 bool 型的 cntj,k,i,若 Ai,j=kcntj,k,i=1,否则 cntj,k,i=0。这里 i 指行,j 指列,k 指值。

考虑计算行 i 的答案。更具体地,我们需要计算哪些行与 i 是相似的。进一步地,我们定义 bool 型的 fj 表示 AiAj 是否是相似的。开始计算前,所有 fj=0

那么类似于暴力,我们需要枚举列 j。此时,显然我们已经明确了 cntj,ai,j,i=1。若我们求出了对于 (i1,i2,,ip) 都满足 cntj,ai,j,ip=1,也就是这些行的第 j 列都是 ai,j,也就是这些行与第 i 行的第 j 列都相同,那么这就意味着,fi1,fi2,,fip 的值都会发生反转。

代码是这样的:

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);

这样的复杂度还是 O(n2m) 的。但是我们就可以将其中的 bool 数组换成 bitset 加速了。具体的,可以发现对于每一个 f1ncntj,k,1n 都是一维的 bool 数组。我们可以将这些一维数组改为 bitset。

那么中间 for (int k = 1; k <= n; ++ k ) f[k] ^= cnt[j][a[i][j]][k]; 就可以改为 f ^= cnt[j][a[i][j]]; 了。总复杂度降低为 O(n2mω)

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 @   2huk  阅读(96)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
点击右上角即可分享
微信分享提示