天天看点

树的直径综合--CH Adera6C

题目描述

Rainbow所在学校的网络中有n台计算机,由n-1条电缆相连(即构成树形)。其中第i条电缆连接ai、bi两台计算机,传输时间为ti。当然,网络中任意两台计算机a、b传输数据所需时间就是a到b的路径上所有电缆的传输时间之和。网络的效率关键在于传输时间最长的两台计算机之间传输数据所需要的时间,记为μ。

现在Rainbow所在的学校要对网络进行升级,升级的目标就是减小μ的值。对于第i条电缆,可以花费pi的价钱把它升级为光缆,光缆依然连接ai和bi,不过传输时间快到可以忽略不计!现在学校要选择一些电缆进行升级,使得升级之后μ的值减小的前提下,花费的价钱最少。

输入格式

第一行一个整数n。

接下来n-1行每行四个整数ai、bi、ti、pi。

输出格式

输出升级之后μ的值减小的前提下,花费的最少价钱。

solution:

树的直径综合--CH Adera6C
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define maxn 100005
using namespace std;
int n,cnt=,head[maxn],dis[maxn],l,r,d,mid,pre[maxn];
int f[maxn],fa[maxn],a[maxn],num1,b[maxn],num2,dp[maxn];
int ans;
bool vis[maxn];

struct EDGE{
  int to,nxt,w,p;
}edge[maxn*];

inline int rd(){
  int x=,f=; char c=' ';
  while(c<'0' || c>'9') {if(c=='-') f=-; c=getchar();}
  while(c<='9' && c>='0') x=x*+c-'0',c=getchar();
  return x*f;
}

void add(int x,int y,int z,int p){
  edge[++cnt].to=y;
  edge[cnt].nxt=head[x];
  edge[cnt].w=z;
  edge[cnt].p=p;
  head[x]=cnt;
}

void init(){
  memset(vis,,sizeof vis); memset(dis,,sizeof dis); memset(pre,,sizeof pre);
  d=; r=l;
}
//求直径
void dfs(int u){
  vis[u]=;
  for(int i=head[u];i;i=edge[i].nxt){
    int v=edge[i].to;
    if(vis[v]) continue;
    if(dis[v]<dis[u]+edge[i].w){
      dis[v]=dis[u]+edge[i].w;
      pre[v]=i;//这个不能放到下面那个if里···pre存的是边
      if(d<=dis[v]) d=dis[v],l=v;
      dfs(v);
    }
  }
}

void get_mid(){//求中点要这么求qwq 新技能get
  for(int i=pre[l];i;i=pre[edge[i^].to]) a[++num1]=i^;
  for(int i=;i<=num1;i++){//而且这样可以遍历直径上所有的点呢!!
    int v=edge[a[i]].to;
    int v2=edge[a[i]^].to;
    if(dis[v]*<dis[l] && *dis[v2]>=dis[l]) mid=v2;
  }
  memset(vis,,sizeof vis); num1=;
}
//求直径的子节点
void dfs_center(int u){
  vis[u]=;
  for(int i=head[u];i;i=edge[i].nxt)
    if(!vis[edge[i].to]){
      int v=edge[i].to;
      dfs_center(v); fa[v]=i;//注意这里fa存的是到v的边
      f[u]=max(f[u],f[v]+edge[i].w);
    }
  vis[u]=;
}

int DP(int u){
  vis[u]=;
  for(int i=head[u];i;i=edge[i].nxt){
    int v=edge[i].to;
    if(!vis[v] && f[v]+edge[i].w==f[u]) dp[u]+=DP(v);
  }
  vis[u]=;
  if(!dp[u]) dp[u]=<<;//注意这里
  return min(dp[u],edge[fa[u]].p);//切断这个点与父亲的边的代价和切断它与在直径上的子树的边的代价取min
}

void solve(){
  dfs_center(mid);
  for(int i=head[mid];i;i=edge[i].nxt)//这里求中点的子节点,a是靠近r的,b是靠近l的
    if(f[edge[i].to]+edge[i].w==dis[mid]) a[++num1]=edge[i].to;
    else if(f[edge[i].to]+edge[i].w==dis[l]-dis[mid]) b[++num2]=edge[i].to;
  if(dis[l]-dis[mid]==dis[mid]){//这是中点两端长度一样的情况
    int mx=;
    for(int i=;i<=num1;i++) {int tmp=DP(a[i]);mx=max(mx,tmp),ans+=tmp;}
    if(num1>) ans-=mx;
  }
  else{
    if(dis[l]-dis[mid]>dis[mid]){//这是左边比右边长
      for(int i=;i<=num1;i++) ans+=DP(a[i]);
      if(ans!=) ans=min(ans,DP(b[]));
      else ans=DP(b[]);
    }
    else{
      for(int i=;i<=num2;i++) ans+=DP(b[i]);
      if(ans!=) ans=min(ans,DP(a[]));
      else ans=DP(a[]);
    }
  }
}

int main(){
  n=rd();
  for(int i=;i<n;i++){
    int x=rd(),y=rd(),z=rd(),p=rd();
    add(x,y,z,p); add(y,x,z,p);
  }
  dfs();
  init();
  dfs(l);//求出直径以及两个端点
  get_mid();//求出中点
  solve();
  printf("%d\n",ans);
  return ;
}