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

算法标签: 贪心, dfsdfsdfs

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

  • 如果发现某个点的子树权值和s3\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

算法标签: 贪心, 换根dpdpdp

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

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

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

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

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

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

在这里插入图片描述

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

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

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

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

另一种解法

*算法标签: 并查集缩点, 换根dpdpdp

将所有颜色想同的连通块缩点, 然后求树的直径长度, 因为上述证明结果还是
⌊k2⌋\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;
}