Shortest Path
题目大意: 给你一颗带权树,保证有偶数个节点n,把这n个节点分成 n/2 对儿,每对儿两点之间的权值为 len,即为原来两点之间路径的边权和,求这 n/2 对儿的 len和 的最小值。
题解: 即为树,那几乎又离不开递归求解,维护答案。此题的关键在于对题目求解问题的转换和分类简化。很显然若要总和最小,那么在分对儿时 两点之间的经过边数不会超过两条 ,所以我们考虑一个 一个父节点 f 和 其孩子 x 之间的边是如何取舍的。那么在树形结构里就是:1、当这个 x 有奇数个孩子的时候,那么这些孩子相邻两个兄弟之间组合后,必会剩下一个与 x 组合,此时 x 便不能再和其父节点 f 组合了,那么父节点就相当于没有这个孩子,其之间的边也不能加上。2、同样的当这个 x 有偶数个孩子的时候, x 必然要和其父节点 f 组合才行,那么这个边就得加上。
总的来说 就是在从叶子节点往上递归时,记录不应该被选的边,最后用整个树的边权,减去这些不应该选的边权即可。
Code:
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
typedef long long LL;
const int N = 1e5+7;
int h[N],e[N*2],ne[N*2],w[N*2],idx; // 无向图
int in[N];
LL ans=0,vis=0;
void add(int a,int b,int v){ // 邻接表
e[idx] = b,w[idx] = v, ne[idx] = h[a],h[a] = idx++;
}
void DFS(int x,int f,int k) // x 是 f 的孩子,k 是之间的边权
{
int sum=0;
for(int i=h[x];i!=-1;i=ne[i]){
int v = e[i],ww = w[i];
if(v==f) continue;
DFS(v,x,ww);
sum += ww;
}
if(in[x]%2==0) ans += sum , in[f]-- , vis += k; //此节点有奇数个孩子,其就父节点少一个孩子,其间的边不选
else ans += sum ;
}
int main()
{
int T;
cin>>T;
while(T--)
{
int n,a,b,wi;
cin>>n;
memset(in,0,sizeof in);
memset(h,-1,sizeof h);
idx = 0; //多组输入,初始化
for(int i=0;i<n-1;i++){
scanf("%d%d%d",&a,&b,&wi);
add(a,b,wi);
add(b,a,wi);
in[a]++,in[b]++;
}
ans=0,vis=0;
DFS(1,0,0);
cout<<ans-vis<<endl; //树边的总和减去不应该被选的边
}
return 0;
}
这里提供另一种角度的代码:
若要使两两之间边权最小,尽量不能选重边,也就是说尽可能在节点所在子树里寻找答案。
DFS统计每个子树大小,偶数不用处理,奇数加边权即可。
#include <iostream>
#include <cstdio>
#include <cstring>
#define ll long long
using namespace std;
const int maxn=1e4+10;
struct edge{
ll t,v,w;
}e[maxn<<1];
ll T,n,tot,ans,hd[maxn];
void add(ll x,ll y,ll z){
tot++;
e[tot].t=hd[x];
e[tot].v=y;
e[tot].w=z;
hd[x]=tot;
}
ll Dfs(ll u,ll fa,ll x){
ll cnt=1;
for(int i=hd[u];i!=0;i=e[i].t)
if(e[i].v!=fa)
cnt+=Dfs(e[i].v,u,e[i].w);
if(cnt%2) ans+=x;
return cnt;
}
int main(){
ios::sync_with_stdio(false);
cin>>T;
ll x,y,z;
while(T--){
memset(hd,0,sizeof(hd));
ans=tot=0;
cin>>n;
for(int i=1;i<n;i++){
cin>>x>>y>>z;
add(x,y,z);
add(y,x,z);
}
Dfs(1,0,0);
cout<<ans<<endl;
}
return 0;
}