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