题解:洛谷 P2700 逐个击破

【题目来源】

洛谷:P2700 逐个击破 - 洛谷

【题目描述】

现在有 \(N\) 个城市,其中 \(K\) 个被敌方军团占领了,\(N\) 个城市间有 \(N-1\) 条公路相连,破坏其中某条公路的代价是已知的,现在,告诉你 \(K\) 个敌方军团所在的城市,以及所有公路破坏的代价,请你算出花费最少的代价将这 \(K\) 个地方军团互相隔离开,以便第二步逐个击破敌人。

【输入】

第一行包含两个正整数 \(N\)\(K\)

第二行包含 \(K\) 个整数,表示哪个城市被敌军占领。

接下来 \(N-1\) 行,每行包含三个正整数 \(a,b,c\),表示从 \(a\) 城市到 \(b\) 城市有一条公路,以及破坏的代价 \(c\)。城市的编号从 \(0\) 开始。

【输出】

输出一行一个整数,表示最少花费的代价。

【输入样例】

5 3
1 2 4
1 0 4
1 3 8
2 1 1
2 4 3

【输出样例】

4

【算法标签】

《洛谷 P2700 逐个击破》 #图论# #树形数据结构#

【代码详解】

#include <bits/stdc++.h>
using namespace std;
#define int long long  // 使用长整型

const int N = 100005;

// 边结构体
struct Edge
{
    int a, b, w;  // 边的两个端点和权重
  
    // 重载小于运算符,用于排序
    bool operator< (const Edge &t) const
    {
        return w < t.w;
    }
} e[N];

int n, k, ans;  // n:节点数, k:特殊节点数, ans:总权重
int p[N], f[2][N];  // f[0]:并查集父节点, f[1]:标记是否为特殊节点

// 自定义比较函数:按权重降序排序
bool cmp(Edge x, Edge y)
{
    return x.w > y.w;
}

/**
 * 并查集查找操作(带路径压缩和标记传递)
 * @param x 要查找的节点
 * @return 节点x的根节点
 */
int find(int x)
{
    // 将当前节点的特殊标记传递给父节点
    f[1][f[0][x]] |= f[1][x];
  
    // 路径压缩
    if (f[0][x] != x)
    {
        f[0][x] = find(f[0][x]);
    }
    return f[0][x];
}

signed main()  // 使用signed代替int(因为定义了#define int long long)
{
    // 输入节点数和特殊节点数
    cin >> n >> k;
  
    // 初始化并查集
    for (int i = 0; i < n; i++)
    {
        f[0][i] = i;  // 每个节点的父节点初始化为自己
    }
  
    // 标记特殊节点
    for (int i = 1; i <= k; i++)
    {
        int u;
        cin >> u;
        f[1][u] = 1;  // 标记为特殊节点
    }
  
    // 输入边信息并计算总权重
    for (int i = 1; i < n; i++)
    {
        int a, b, w;
        cin >> a >> b >> w;
        e[i] = {a, b, w};
        ans += w;  // 累加所有权重
    }
  
    // 将边按权重降序排序(从大到小)
    sort(e + 1, e + n, cmp);
  
    // 调试输出(注释掉的代码)
    // for (int i=1; i<n; i++)
    //     cout << e[i].a << " " << e[i].b << " " << e[i].w << endl;
  
    // 构建最大生成森林(避免连接两个特殊节点)
    for (int i = 1; i < n; i++)
    {
        int a = find(e[i].a);  // 查找起点所在集合的根
        int b = find(e[i].b);  // 查找终点所在集合的根
        int w = e[i].w;        // 当前边的权重
      
        // 如果两个集合都包含特殊节点,跳过这条边(避免连接特殊节点)
        if (f[1][a] && f[1][b])
        {
            continue;
        }
      
        // 合并两个集合
        f[0][b] = a;
        ans -= w;  // 减去使用的边权(因为要最大化剩余权重)
      
        // 更新标记(确保特殊标记正确传递)
        find(b);
    }
  
    // 输出剩余的总权重
    cout << ans << endl;
  
    return 0;
}

【运行结果】

5 3
1 2 4
1 0 4
1 3 8
2 1 1
2 4 3
4
posted @ 2026-02-19 21:26  团爸讲算法  阅读(1)  评论(0)    收藏  举报