Acwing158周赛题解

A

算法标签: 基础算法

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 3;

int w[N];

int main() {
    ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);

    for (int i = 0; i < 3; ++i) cin >> w[i];
    sort(w, w + N);

    if (w[0] + w[1] == w[2]) cout << "YES" << "\n";
    else cout << "NO" << "\n";

    return 0;
}

B

算法标签: 贪心, d f s dfs dfs

删除两条树边, 使得树分为三个部分, 要求每一部分的权值恰好是 s 3 \frac {s}{3} 3s, 其中 s s s为树的节点的权值和
因为点的数量是 1 0 6 10 ^ 6 106, 因此不能暴力枚举所有边
将问题细分, 将问题分为每个点所在子树的权值和

  • 如果发现某个点的子树权值和 s 3 \frac {s}{3} 3s, 那么将当前子树与父节点进行分割
  • 然后回溯向上到根节点

在这里插入图片描述
为什么这样一定能找到全部的方案?
在这里插入图片描述
如果剩余两部分不在同一个分支, 那么由上图一定能找到答案, 因为回溯的过程是独立的

在这里插入图片描述
那么如果剩余两部分在同一分支, 由于回溯过程是从子节点到父节点进行的, 也可以找到, 因此该方案是可以找到全部方案的

#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>

using namespace std;

const int N = 1e6 + 10, M = N << 1;

int n;
int head[N], edge_end[M], next_edge[M], edge_index;
int w[N], sum;
vector<int> ans;

void add(int ver1, int ver2) {
	edge_end[edge_index] = ver2, next_edge[edge_index] = head[ver1], head[ver1] = edge_index++;
}

// 计算以u为根节点的子树的权值之和
int dfs(int u) {
	int s = w[u];
	for (int i = head[u]; ~i; i = next_edge[i]) {
		int ver = edge_end[i];
		int val = dfs(ver);
		if (val == sum / 3) ans.push_back(ver);
		else s += val;
	}
	return s;
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);

	memset(head, -1, sizeof head);

	int root;
	cin >> n;

	for (int i = 1; i <= n; ++i) {
		int fa;
		cin >> fa >> w[i];
		if (fa == 0) root = i;
		else add(fa, i);
		sum += w[i];
	}

	if (sum % 3) {
		cout << -1 << "\n";
		return 0;
	}

	dfs(root);

	if (ans.size() < 2) cout << -1 << "\n";
	else {
		for (int i = 0; i < 2; ++i) cout << ans[i] << " ";
		cout << "\n";
	}
	return 0;
}

C

算法标签: 贪心, 换根 d p dp dp

从黑色点只能到黑色点, 从白色点只能到白色点, 最少操作多少次, 将树变成同一种颜色

首先考虑简单情况, 将树中黑白交替次数最多的链找出
在这里插入图片描述

段与段之间都是独立的, 因为没有环, 将 k k k段变为 1 1 1

不管如何操作, 最多减少两段, 那么操作次数的下限就是
⌊ k 2 ⌋ \left \lfloor \frac {k}{2} \right \rfloor 2k

如果从最中心段开始变换, 中心段到两端的最远距离 ⌊ k 2 ⌋ \left \lfloor \frac {k}{2} \right \rfloor 2k

对于整棵树的操作次数
≥ ⌊ k 2 ⌋ \ge \left \lfloor \frac {k}{2} \right \rfloor 2k

在这里插入图片描述

由于选择的链是交替次数最多的链, 对于链以外的点的变化次数一定不能大于链两端的变化次数, 如果大于, 那么该点所在的分支就会替代原来的链

在这里插入图片描述
就会变为上述红色链
整棵树的变化次数最小值就是
⌊ k 2 ⌋ \left \lfloor \frac {k}{2} \right \rfloor 2k

段数 = 颜色变化次数 + 1 段数 = 颜色变化次数 + 1 段数=颜色变化次数+1, 因此定义树的边权, 如果边的两端颜色相同边权为 0 0 0, 否则为 1 1 1, 将问题转化为树的直径

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 2e5 + 10, M = N << 1;

int n;
int w[N];
int head[N], edge_end[M], next_edge[M], edge_index;
int res;

void add(int ver1, int ver2) {
	edge_end[edge_index] = ver2, next_edge[edge_index] = head[ver1], head[ver1] = edge_index++;
}

int dfs(int u, int fa) {
	int d1 = 0, d2 = 0;

	for (int i = head[u]; ~i; i = next_edge[i]) {
		int ver = edge_end[i];
		if (ver == fa) continue;

		int val = dfs(ver, u) + (w[u] != w[ver]);
		if (val > d1) d2 = d1, d1 = val;
		else if (val > d2) d2 = val;
	}

	res = max(res, d1 + d2);
	return d1;
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);

	memset(head, -1, sizeof head);

	cin >> n;
	for (int i = 1; i <= n; ++i) cin >> w[i];
	for (int i = 0; i < n - 1; ++i) {
		int u, v;
		cin >> u >> v;
		add(u, v);
		add(v, u);
	}

	dfs(1, -1);

	// 树的直径等同颜色变化次数, 颜色变化次数次 + 1 == 段数
	res = (res + 1) / 2;
	cout << res << "\n";

	return 0;
}

另一种解法

*算法标签: 并查集缩点, 换根 d p dp dp

将所有颜色想同的连通块缩点, 然后求树的直径长度, 因为上述证明结果还是
⌊ k 2 ⌋ \left \lfloor \frac {k}{2} \right \rfloor 2k

#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <unordered_set>

using namespace std;

typedef pair<int, int> PII;
typedef long long LL;
const int N = 2e5 + 10, M = N << 1;

int n;
int w[N];
int head1[N], head2[N], edge_end[M], next_edge[M], edge_index;
int fa[N];
bool vis[N];
PII edges[M];
int res;
unordered_set<LL> set;

void add(int head[], int ver1, int ver2) {
    edge_end[edge_index] = ver2, next_edge[edge_index] = head[ver1], head[ver1] = edge_index++;
}

int find(int u) {
    if (fa[u] != u) fa[u] = find(fa[u]);
    return fa[u];
}

// 合并两个点
void merge(int u, int v) {
    int fa1 = find(u);
    int fa2 = find(v);
    fa[fa2] = fa1;
}

// 求树的直径
int dfs(int u, int fa) {
    int d1 = 0, d2 = 0;

    for (int i = head2[u]; ~i; i = next_edge[i]) {
        int ver = edge_end[i];
        if (ver == fa) continue;

        int val = dfs(ver, u) + 1;
        if (val > d1) d2 = d1, d1 = val;
        else if (val > d2) d2 = val;
    }
    res = max(res, d1 + d2);
    return d1;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    memset(head1, -1, sizeof head1);
    memset(head2, -1, sizeof head2);

    cin >> n;
    for (int i = 1; i <= n; ++i) fa[i] = i;
    for (int i = 1; i <= n; ++i) cin >> w[i];

    for (int i = 0; i < n - 1; ++i) {
        int u, v;
        cin >> u >> v;
        add(head1, u, v);
        add(head1, v, u);
        if (w[u] == w[v]) merge(u, v);
        edges[i] = {u, v};
    }

    for (int i = 0; i < n - 1; ++i) {
        int u = find(edges[i].first);
        int v = find(edges[i].second);
        if (u != v && set.count((LL) u * N + v) == 0 && set.count((LL) v * N + u) == 0) {
            add(head2, u, v);
            add(head2, v, u);
            set.insert((LL) u * N + v);
            set.insert((LL) v * N + u);
        }
    }
    
    dfs(find(n), -1);

    cout << (res + 1) / 2 << "\n";
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值