题目还是有点坑的,我一开始以为删掉一些边,点也没了,不知道我是为什么会这么想…
其实这道题的意思是,给你一个树,让你删掉一些边,使得由树删掉边剩余的多个连通块每个连通块的权值和是k的倍数,求满足条件的剩余的边的最小权值和。注意还有一个条件:总的点权和就是k的倍数
当一道题没有思路的时候,我们考虑所有的可能的性质。
我们发现了一个性质:如果一个点的权值已经是k的倍数,我们可以直接把这条边删掉,也就是最小边权和不要这个点所链接的所有的边。这样剩下的点的权值和还是k的倍数(因为总的点权和就是k的倍数)。我们推广一下这个性质,也就是说所有权值和为k的倍数的连通块都可以不要,都可以单独隔开,也就是说这个权值和为k的倍数的连通块向外连的边我们都可以不要,所以我们可以直接dfs。维护一个当前连通块的权值和,最后回溯的时候,如果权值是k的倍数,我们就不要这个连通块的这条边,如果不是k的倍数,我们就脸上这个点,加上这个点的权值。最后总权值就是答案。
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const int N = 1e6 + 7, M = 5e6 + 7;
int n, m, k, tot;
int head[N], ver[M], nex[M];
ll edge[M];
ll node[N];
ll sum[N], res;
bool vis[N];
void add(int x, int y, int z)
{
ver[tot] = y;
edge[tot] = z;
nex[tot] = head[x];
head[x] = tot ++;
}
void dfs(int x, int fa, int edge_id)
{
vis[x] = 1;
for(int i = head[x];~i; i = nex[i]){
int y = ver[i];
if(y != fa && !vis[y]){
dfs(y, x, i);
}
}
if(node[x] % k == 0)return ;
res += edge[edge_id];
node[fa] += node[x];
}
int main()
{
memset(head, -1, sizeof head);
scanf("%d%d", &n, &k);
for(int i = 1; i <= n; ++ i)
scanf("%lld", &node[i]);
for(int i = 1; i <= n - 1; ++ i){
int x, y;
ll z;
scanf("%d%d%lld", &x, &y, &z);
add(x, y, z);add(y, x, z);
}
dfs(1, 1, 1);
printf("%lld\n", res);
return 0;
}