hdu6769 In Search of Gold

题目链接
二分答案。
然后用dp来check,
dp x y表示x节点,子树中用了y个来自a的边的离x节点最远的点的距离的最小值。
转移的时候,只合并直径小于mid的情况。

#include 
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
#define fi first
#define se second
#define pb push_back
#define mp make_pair
int t,n,ka;
const LL inf=1e18;
struct uzi{
  int x,a,b;  
};
vector<uzi>v[N];
LL dp[N][26];
int sz[N];
LL tmp[26],mid;
void dfs(int x,int y){
  sz[x]=1;int st=0;
  for(auto k:v[x]){
    if(k.x!=y){
      dfs(k.x,x);
      int nx=min(ka,sz[k.x]);//子树sz
      int re=min(sz[x],ka);//自己当前已经合并过的
      int can=min(ka,nx+re+1);//可以用到最多的
      sz[x]+=sz[k.x];
      for(int i=0;i<=can;i++)tmp[i]=inf;//转移数组
      if(!st){
        for(int i=0;i<=nx;i++){
          if(i+1<=ka){
            tmp[i+1]=min(tmp[i+1],dp[k.x][i]+k.a);
          }
          tmp[i]=min(tmp[i],dp[k.x][i]+k.b);
        }
        for(int i=0;i<=nx+1;i++){
          dp[x][i]=tmp[i];
        }
        st=1;
        continue;
      }
      for(int i=0;i<=re;i++){
        for(int j=0;j<=nx&&j+i<=can;j++){
          //如果能用a数组的边
          if(dp[x][i]+dp[k.x][j]+k.a<=mid){//转移合法
            tmp[i+j+1]=min(tmp[i+j+1],max(dp[x][i],dp[k.x][j]+k.a));
          }
          if(dp[x][i]+dp[k.x][j]+k.b<=mid){
            tmp[i+j]=min(tmp[i+j],max(dp[x][i],dp[k.x][j]+k.b));
          }         
        }
      }
      for(int i=0;i<=can;i++){
        dp[x][i]=tmp[i];
      }
      sz[x]=can;
    }
  }
  if(v[x].size()==1 && y) dp[x][0]=0;
}
int check(LL x){
  dfs(1,0);
  return dp[1][ka]<=x;
}
int main() {
  ios::sync_with_stdio(false);
  for(cin>>t;t;t--){
    cin>>n>>ka;
    for(int i=1;i<=n;i++){
      for(int j=0;j<=ka;j++)dp[i][j]=inf;
      v[i].clear();
    }
    LL l=1,r=0,ans;
    for(int i=1;i<n;i++){
      int s,t,a,b;
      cin>>s>>t>>a>>b;
      v[s].pb({t,a,b});
      v[t].pb({s,a,b});
      r+=max(a,b);
    }
    while(l<=r){
      mid=l+r>>1;
      if(check(mid))ans=mid,r=mid-1;
      else l=mid+1;
    }
    cout<<ans<<'\n';
  }
  return 0;
}

你可能感兴趣的:(二分,dp)