This commit is contained in:
Zengtudor 2024-11-19 10:44:23 +08:00
parent 97dcb4586e
commit d276eea07c
2 changed files with 299 additions and 58 deletions

View File

@ -1,46 +1,98 @@
#include <cctype>
#include <cstdint>
#include <iostream>
#include <utility>
#include <vector>
#include <map>
using ll = int64_t;
using std::cin, std::cout;
const ll maxn = 5e5+5;
ll n, k, a[maxn], ans[maxn];
std::vector<std::pair<ll, ll>> adj[maxn];
std::map<ll,ll> dp[maxn];//dp[i][j]:第i个节点距离颜色j的最大值
void dfs(const ll &fth, const ll &u)noexcept{
dp[u].emplace(a[u],0);
// dp[u][a[u]]=0;
for(const auto& [v,w]:adj[u]){
if(v==fth)continue;
dfs(u,v);
for(const auto& [kk,vv]:dp[v]){
if((dp[u][kk]>0||a[u]==kk)&&(dp[v][kk]>0||a[v]==kk)){
//更新与u,v子树有关的红温值
ans[kk]=std::max(ans[kk], dp[u][kk] + dp[v][kk] + w);
}
dp[u][kk]=std::max(dp[u][kk],dp[v][kk]+w);
if(a[u]==kk)ans[kk]=std::max(ans[kk],dp[u][kk]);//更新当前节点的红温值
// 定义最大节点数为5e5加5以确保足够的空间
const ll maxn = 5e5 + 5;
ll n, k; // n: 树的节点数k: 暴龙的种类数
ll a[maxn]; // a[i]: 第i个节点的暴龙种类
ll ans[maxn]; // ans[i]: 第i种暴龙的红温值
std::vector<std::pair<ll, ll>> adj[maxn]; // adj[u]: 节点u的邻居及边权
std::map<ll, ll> dp[maxn]; // dp[u][j]: 节点u到子树中种类j的最大距离
// 优化输入读取提高IO速度
struct CinNum {
char c;
ll n, w;
// 重载右移运算符以自定义输入方式
CinNum &operator>>(ll &num) {
c = n = 0;
w = 1;
// 跳过非数字字符,记录负号
while (!isdigit(c = getchar())) {
if (c == '-') w = -1;
}
// 读取数字
while (isdigit(c)) {
n = n * 10 + (c - '0');
c = getchar();
}
num = n * w;
return *this;
}
} cinn;
// 使用自定义的CinNum来替代标准输入
#define cin cinn
/**
* @brief 使 (DFS)
*
* @param fth
* @param u
*/
void dfs(const ll &fth, const ll &u) noexcept {
// 初始化当前节点的dp自己到自己距离为0
dp[u].emplace(a[u], 0);
// 遍历所有邻接节点
for (const auto& [v, w] : adj[u]) {
if (v == fth) continue; // 避免回到父节点
dfs(u, v); // 递归处理子节点
// 遍历子节点v的所有暴龙种类及其对应的最大距离
for (const auto& [kk, vv] : dp[v]) {
// 如果当前节点u已经有这种暴龙类型且子节点v也有
if ((dp[u][kk] > 0 || a[u] == kk) && (dp[v][kk] > 0 || a[v] == kk)) {
// 更新ans[kk]为u和v子树中这种暴龙类型的最大距离
ans[kk] = std::max(ans[kk], dp[u][kk] + dp[v][kk] + w);
}
// 更新u节点到这种暴龙类型的最大距离
dp[u][kk] = std::max(dp[u][kk], dp[v][kk] + w);
// 如果当前节点u本身就是这种暴龙类型进一步更新ans[kk]
if (a[u] == kk) {
ans[kk] = std::max(ans[kk], dp[u][kk]);
}
}
// 清空子节点v的dp以节省内存因为已经合并到u的dp中了
dp[v].clear();
}
}
int main(){
std::iostream::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
cin>>n>>k;
for(ll i{1};i<=n;i++)cin>>a[i];
for(ll i{1};i<n;i++){
ll u,v,w;
cin>>u>>v>>w;
adj[u].emplace_back(v,w);
adj[v].emplace_back(u,w);
// 读取节点数n和暴龙种类数k
cin >> n >> k;
// 读取每个节点的暴龙种类
for(ll i = 1; i <= n; i++) cin >> a[i];
// 读取树的边信息,并构建邻接表
for(ll i = 1; i < n; i++){
ll u, v, w;
cin >> u >> v >> w;
adj[u].emplace_back(v, w);
adj[v].emplace_back(u, w);
}
dfs(0,1);
for(ll i{1};i<=k;i++)cout<<ans[i]<<'\n';
}
// 从节点1开始DFS遍历整棵树假设节点1为根节点父节点为0
dfs(0, 1);
// 输出每种暴龙的红温值
for(ll i = 1; i <= k; i++) cout << ans[i] << '\n';
}

View File

@ -1,38 +1,227 @@
这段代码中的 `if` 语句主要是用于确保在更新暴龙的红温值 (最大距离) 时判断当前的节点类型与子树中的节点类型之间的关系。具体来说,这里是在处理当前节点 `u` 和子节点 `v` 之间的距离关系,同时考虑了他们的暴龙类型。
# 暴龙
我们来逐步解析这个 `if` 语句及其包含的逻辑:
## 题目背景
Hanghang 红温了,然后他变异成了暴龙。
## 题目描述
机房里有 $k$ 种暴龙。现在他们占据了一棵树上的所有的点。这棵树有 $n$ 个点,边有边权。现在对于某一种暴龙,他们的红温值定义为任意两个这种暴龙的距离的最大值。如果这种暴龙的出现次数小于 $2$,则红温值为 $0$。考拉想要证明 Hanghang 红温了,因此他想要问你每种暴龙的红温值。
## 输入格式
第一行两个正整数 $n,k$,表示树的节点数和暴龙的种类数。
第二行 $n$ 个在 $[1,k]$ 间的正整数 $p_i$,表示每个节点的暴龙的种类。
接下来 $n-1$ 行,每行三个正整数 $a,b,c$,表示 $a,b$ 之间有一条边,边权为 $c$。保证这些边构成一棵树。
## 输出格式
输出 $k$ 行,每行一个数表示种类为 $i$ 的暴龙的红温值。
## 样例 #1
### 样例输入 #1
```
6 3
2 3 1 1 2 3
1 2 1
1 3 1
2 5 1
3 4 1
3 6 1
```
### 样例输出 #1
```
1
2
3
```
## 提示
【样例 2】
见选手目录下的 tree/tree2.in 与 tree/tree2.ans。
该样例满足子任务 1 的限制。
【样例 3】
见选手目录下的 tree/tree3.in 与 tree/tree3.ans。
该样例满足子任务 2 的限制。
【样例 4】
见选手目录下的 tree/tree4.in 与 tree/tree4.ans。
该样例满足子任务 3 的限制。
【样例 5】
见选手目录下的 tree/tree5.in 与 tree/tree5.ans。
该样例满足子任务 4 的限制。
本题使用捆绑测试。
| 子任务编号 | $n$ | 分值 |
| :----------: | :----------: | :----------: |
| 1 |$3000$ | 5 |
| 2| $10^5$ | 10|
| 3 | $3\times 10^5$ | 10 |
| 4 | $5\times 10^5$ | 75 |
对于所有数据,$1\leq p_i \leq k\leq n\leq 5\times 10^5,1\leq a,b\leq n,1\leq c\leq 10^9$。
本题输入输出量较大,请选手使用较快的 IO 方式。
本题下发文件:[https://git.zziyu.cn/Zengtudor/algorithm_2024/src/branch/main/src/20241118/tree](https://git.zziyu.cn/Zengtudor/algorithm_2024/src/branch/main/src/20241118/tree)(打开后预览)
# 25pts暴力代码与解析
>关于使用ai
>
>1. 下面的代码是本人手写
>2. 解析由ai生成模型为chatgpt o1-mini
```cpp
if((a[u] == kk || dp[u][kk] > 0) && (a[v] == kk || dp[v][kk] > 0)) {
ans[kk] = max(ans[kk], dp[u][kk] + dp[v][kk] + w);
#include <cctype>
#include <cstdint>
#include <iostream>
#include <utility>
#include <vector>
#include <map>
using ll = int64_t;
using std::cin, std::cout;
// 定义最大节点数为5e5加5以确保足够的空间
const ll maxn = 5e5 + 5;
ll n, k; // n: 树的节点数k: 暴龙的种类数
ll a[maxn]; // a[i]: 第i个节点的暴龙种类
ll ans[maxn]; // ans[i]: 第i种暴龙的红温值
std::vector<std::pair<ll, ll>> adj[maxn]; // adj[u]: 节点u的邻居及边权
std::map<ll, ll> dp[maxn]; // dp[u][j]: 节点u到子树中种类j的最大距离
// 优化输入读取提高IO速度
struct CinNum {
char c;
ll n, w;
// 重载右移运算符以自定义输入方式
CinNum &operator>>(ll &num) {
c = n = 0;
w = 1;
// 跳过非数字字符,记录负号
while (!isdigit(c = getchar())) {
if (c == '-') w = -1;
}
// 读取数字
while (isdigit(c)) {
n = n * 10 + (c - '0');
c = getchar();
}
num = n * w;
return *this;
}
} cinn;
// 使用自定义的CinNum来替代标准输入
#define cin cinn
/**
* @brief 使用深度优先搜索 (DFS) 计算每种暴龙的红温值
*
* @param fth 当前节点的父节点
* @param u 当前处理的节点
*/
void dfs(const ll &fth, const ll &u) noexcept {
// 初始化当前节点的dp自己到自己距离为0
dp[u].emplace(a[u], 0);
// 遍历所有邻接节点
for (const auto& [v, w] : adj[u]) {
if (v == fth) continue; // 避免回到父节点
dfs(u, v); // 递归处理子节点
// 遍历子节点v的所有暴龙种类及其对应的最大距离
for (const auto& [kk, vv] : dp[v]) {
// 如果当前节点u已经有这种暴龙类型且子节点v也有
if ((dp[u][kk] > 0 || a[u] == kk) && (dp[v][kk] > 0 || a[v] == kk)) {
// 更新ans[kk]为u和v子树中这种暴龙类型的最大距离
ans[kk] = std::max(ans[kk], dp[u][kk] + dp[v][kk] + w);
}
// 更新u节点到这种暴龙类型的最大距离
dp[u][kk] = std::max(dp[u][kk], dp[v][kk] + w);
// 如果当前节点u本身就是这种暴龙类型进一步更新ans[kk]
if (a[u] == kk) {
ans[kk] = std::max(ans[kk], dp[u][kk]);
}
}
// 清空子节点v的dp以节省内存因为已经合并到u的dp中了
dp[v].clear();
}
}
int main(){
// 读取节点数n和暴龙种类数k
cin >> n >> k;
// 读取每个节点的暴龙种类
for(ll i = 1; i <= n; i++) cin >> a[i];
// 读取树的边信息,并构建邻接表
for(ll i = 1; i < n; i++){
ll u, v, w;
cin >> u >> v >> w;
adj[u].emplace_back(v, w);
adj[v].emplace_back(u, w);
}
// 从节点1开始DFS遍历整棵树假设节点1为根节点父节点为0
dfs(0, 1);
// 输出每种暴龙的红温值
for(ll i = 1; i <= k; i++) cout << ans[i] << '\n';
}
```
### 变量说明
### 代码详解
- `kk`: 表示当前正在处理的暴龙类型的标识符(通常是种类编号)。
- `a[u]`: 当前节点 `u` 的暴龙类型。
- `dp[u][kk]`: 从当前节点 `u` 开始的子树中,该暴龙类型 `kk` 的最大距离。
- `dp[v][kk]`: 从子节点 `v` 开始的子树中,该暴龙类型 `kk` 的最大距离。
- `w`: 当前节点 `u` 和子节点 `v` 之间的边的权重(即两者之间的距离)。
- `ans[kk]`: 用于存储当前暴龙类型 `kk` 的红温值(最大距离)。
1. **数据结构选择**:
- **邻接表**: 使用`std::vector<std::pair<ll, ll>> adj[maxn];`来存储树的邻接表,每个节点存储与之相连的节点及边权。
- **动态规划表DP**: 使用`std::map<ll, ll> dp[maxn];`来存储每个节点到其子树中不同暴龙种类的最大距离。这里使用`std::map`是因为暴龙种类k可能较多且不连续。
### 逻辑解释
2. **输入优化**:
- 定义了一个自定义的`CinNum`结构体,通过重载`>>`运算符来快速读取整数。这在处理大数据量时能够显著提高输入速度。
1. **类型匹配**:
- `(a[u] == kk || dp[u][kk] > 0)`:
- 这个条件检查当前节点 `u` 是否是类型 `kk` 或者在 `u` 的子树中已经存在类型 `kk` 的暴龙。如果存在,可以考虑将 `u``v` 之间的距离计算入 `ans[kk]`
3. **深度优先搜索DFS**:
- **初始化**: 对于每个节点u将自己的暴龙种类`a[u]`加入`dp[u]`初始距离为0。
- **递归处理子节点**: 对每个子节点v递归调用`dfs(u, v)`。
- **合并子节点DP**: 对于子节点v的每个暴龙种类kk检查当前节点u是否也有这种暴龙。如果有则计算通过边(u, v)连接后的距离,并更新`ans[kk]`为更大的值。
- **更新u节点的DP**: 将子节点v的距离加上边权w与当前的`dp[u][kk]`比较,取较大者。
- **清理资源**: 处理完子节点v后清空`dp[v]`以节省内存。
- `(a[v] == kk || dp[v][kk] > 0)`:
- 这个条件检查子节点 `v` 是否是类型 `kk` 或者在 `v` 的子树中已经存在类型 `kk` 的暴龙。类似地,如果存在,也可以考虑将 `u``v` 之间的距离计算入 `ans[kk]`
2. **计算距离**:
- 如果以上两个条件都是满足的,表示 `u``v` 之间的边可以被计算在类型 `kk` 的红温值中,因此最大距离被更新为:
```cpp
ans[kk] = max(ans[kk], dp[u][kk] + dp[v][kk] + w);
```
- 这里的 `dp[u][kk] + dp[v][kk] + w` 即为通过当前边 `u-v` 连接的所有类型为 `kk` 的暴龙之间的距离。
4. **输出结果**:
- 遍历每种暴龙种类输出其对应的红温值。若某种暴龙在树中数量少于2则红温值保持初始化的0。
### 总结
这个 `if` 判断的作用在于决定是否可以将当前边 `u-v` 的权重 `w` 加入到 `ans[kk]` 中,从而更新当前暴龙类型 `kk` 的最大红温值。这确保了只有在当前节点及其子节点之间具有相同暴龙类型时,才会合并和计算它们之间的边的影响。这样能够更精确地得到不同类型暴龙之间的距离关系。
该算法利用深度优先搜索DFS结合动态规划DP的思想高效地计算了树中每种暴龙类型的最大距离红温值。具体步骤如下
1. **DFS遍历**: 从根节点开始,递归遍历整个树结构。
2. **动态规划**: 在遍历的过程中,维护每个节点到其子树中特定暴龙类型的最大距离。
3. **答案更新**: 通过合并子节点的信息,动态地更新每种暴龙类型的最大距离。
4. **复杂度分析**:
- 对于每个节点可能需要处理最多k种暴龙类型。因此时间复杂度在最坏情况下为O(nk)。由于k和n均可达5e5这种方法在大规模数据下会超过时间限制。
5. **适用性**:
- 该代码适用于子任务1规模较小n ≤ 3000的情况能够在有限的时间内完成计算。
- 对于更大规模的数据需要优化数据结构和算法例如使用更高效的方式存储DP信息如并查集、最低公共祖先LCA等技术以降低时间和空间复杂度。
希望这些解释和注释对您理解和优化代码有所帮助!如果有进一步的问题,欢迎继续交流。