【每日一题】转换思维/树上dfs
是我最喜欢的树上dfs~
给你一棵 n 个节点的树(保证 n是偶数),你需要将 nn个节点分为 n/2 个点对,使得每个点对的两个点的距离的和最小。
关键点:思维转换:选点两两配对求距离和--》转换成选边
- 在最短的距离和之中一条边一定不会被覆盖两次,也就是说对于一条边来说,其实只有选和不选两种可能。
- 对于一个点x,它子树中的点一定会尽量在子树中找到匹配的点内部消化掉(要么连父亲要么连兄弟),只有根是有可能会往上找一个点来匹配(不然又会出现重复覆盖一条边的情况)。
这道题最关键的是转换思维,如果考虑两两配对并不容易编码,而是直接考虑边,边只有选和不选两种可能;
对于当前点 x,如果它的子树(包括它自己)有偶数个点,那么肯定在子树里面就互相连完了,它不需要向上连;如果是奇数个点,x 就需要去匹配上面的点了,所以 x向它父亲连的边就要选。#include<bits/stdc++.h> using namespace std; #define ll long long #define MAXN 20010 ll p[MAXN],h[MAXN],ne[MAXN],edgew[MAXN]; ll num=0; void addEdge(int from, int to,int w) { p[++num] = to; ne[num] = h[from]; h[from] = num; edgew[num] = w; p[++num] = from; ne[num] = h[to]; h[to] = num; edgew[num] = w; } ll ans=0; ll cnt[MAXN>>1];//记录子树的节点数量 void dfs(int u,int father,int faw){ cnt[u]=1; for(int i=h[u];i;i=ne[i])if(p[i]!=father) { dfs(p[i],u,edgew[i]); cnt[u]+=cnt[p[i]]; } if(cnt[u]&1) ans+=faw;//加上和父亲的边 } int main() { int t; cin>>t; while(t--) { ans = 0; num=0; int n;cin>>n; memset(h, 0, sizeof(h)); memset(cnt, 0, sizeof(cnt)); for(int i = 1; i <= n-1; i++) { int x,y,w; scanf("%d%d%d", &x, &y, &w); addEdge(x, y, w); } dfs(1, 0, 0); printf("%lld\n", ans); } }