题目链接:http://acm.hdu.edu.cn/contests/contest_showproblem.php?pid=1007&cid=880
题意:给你一个n个点,n-1条边的树,每条边的长度可能是a[i],也可能是b[i],有k条边的长度是取a[i],n-k-1条边的长度是取b[i]。然后要你求这棵树可能的最小直径。
思路:要求可能的最小直径,可以想到二分数的直径看是否满足。二分看是否满足时可以用树型DP, f[i][j] 表示在 i 的子树,其中有 j 条边的的长度是取 a 数组的所有树直径 ≤ mid 的方案中,与 i 点距离最远的点到 i 的距离的最小
#include<bits/stdc++.h> using namespace std; typedef long long ll; int g[20005],v[40005],a[40005],b[40005],nxt[40005],siz[20005]; ll f[20005][25],h[20005]; int n,m; void dfs(int x,int y,ll mid) { siz[x]=f[x][0]=0; for(int i=g[x]; i; i=nxt[i]) { int u=v[i]; if(u==y) continue; dfs(u,x,mid); int A=a[i],B=b[i],pre=siz[x],cur=siz[u]; int now=min(pre+cur+1,m); for(int j=0; j<=now; j++) h[j]=mid+1; for(int j=0; j<=pre; j++) for(int k=0; k<=cur&&j+k<=m; k++) { if(f[x][j]+f[u][k]+A<=mid) h[j+k+1]=min(h[j+k+1],max(f[x][j],f[u][k]+A)); if(f[x][j]+f[u][k]+B<=mid) h[j+k]=min(h[j+k],max(f[x][j],f[u][k]+B)); } siz[x]=now; for(int j=0; j<=now; j++) f[x][j]=h[j]; } } int main() { int t; cin>>t; while(t--) { scanf("%d%d",&n,&m); ll l=0,r=0; for(int i=0; i<=n; i++) g[i]=0; int ed=0; for(int i=1; i<n; i++) { int x,y,A,B; scanf("%d%d%d%d",&x,&y,&A,&B); v[++ed]=y; a[ed]=A; b[ed]=B; nxt[ed]=g[x]; g[x]=ed; v[++ed]=x; a[ed]=A; b[ed]=B; nxt[ed]=g[y]; g[y]=ed; r+=max(A,B); } ll ans=0; while(l<=r) { ll mid=(l+r)/2; dfs(1,0,mid); if(f[1][m]<=mid) { ans=mid; r=mid-1; } else l=mid+1; } printf("%lld\n",ans); } }
可能值。然后转移时合并之前已经 DP过的部分和新加入的子树,如果两部分到 i 的最长距离之和超过 mid 则这颗树的直径已经超过了mid,就不需要转移了。