题解【洛谷P2700】逐个击破

题面

和 [JSOI2008]星球大战 类似的套路,都是运用 反向思维 的好题。

首先假设所有边都被破坏,然后减去一些不需要被破坏的边的花费就是答案。

注意我们要求答案最小,就一定要减去边权尽量大的边,因此我们就需要将边权从大到小排序后再处理。

讲一下具体判断一条边是否不需要被破坏的方式:

  • 设一个数组 \(vis_i\),表示以 \(i\) 为代表的连通块中是否存在要被破坏的城市。\(fa_i\) 表示 \(i\) 号城市所在的集合的代表,初始 \(fa_i=i\)其实就是并查集
  • 对于一条边的端点 \(u\)\(v\),如果 \(vis_{u 的祖先}\)\(vis_{v 的祖先}\) 不都为 true,那么这一条边就可以被破坏。还需要进行一些后续的操作。

代码写起来很简单。

#include 

using namespace std;

typedef long long LL;

inline int gi()
{
    int f = 1, x = 0; char c = getchar();
    while (c < '0' || c > '9') {if (c == '-') f = -1; c = getchar();}
    while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return f * x;
}

const int INF = 0x3f3f3f3f, N = 100003, M = N << 1;

int n, k;
int a[N];
int tot, head[N], ver[M], nxt[M], edge[M];
int fa[N];
LL sum;
bool vis[N];
struct Node
{
	int u, v, w;
} x[N];

inline void add(int u, int v, int w)
{
	ver[++tot] = v, edge[tot] = w, nxt[tot] = head[u], head[u] = tot;
}

inline bool cmp(Node x, Node y)
{
	return x.w > y.w;
}

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

int main()
{
    n = gi(), k = gi();
    for (int i = 1; i <= k; i+=1) a[i] = gi() + 1, vis[a[i]] = true;
    for (int i = 1; i <= n; i+=1)
    	fa[i] = i;
    for (int i = 1; i < n; i+=1)
    {
    	int u = gi() + 1, v = gi() + 1, w = gi();
    	add(u, v, w), add(v, u, w);
    	x[i] = (Node){u, v, w};
    	sum += w;
    }
    sort(x + 1, x + n, cmp);
    for (int i = 1; i < n; i+=1)
    {
    	int u = getf(x[i].u), v = getf(x[i].v);
    	if (!(vis[u] && vis[v]))
    	{
    		fa[u] = v;
    		vis[v] |= vis[u];
    		sum -= x[i].w;
    	}
    }
    printf("%lld\n", sum);
    return 0;
}

你可能感兴趣的:(题解【洛谷P2700】逐个击破)