树的直径综合--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
这里写图片描述

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define maxn 100005
using namespace std;
int n,cnt=1,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*2];

inline int rd(){
  int x=0,f=1; char c=' ';
  while(c<'0' || c>'9') {if(c=='-') f=-1; c=getchar();}
  while(c<='9' && c>='0') x=x*10+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,0,sizeof vis); memset(dis,0,sizeof dis); memset(pre,0,sizeof pre);
  d=0; r=l;
}
//求直径
void dfs(int u){
  vis[u]=1;
  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^1].to]) a[++num1]=i^1;
  for(int i=1;i<=num1;i++){//而且这样可以遍历直径上所有的点呢!!
    int v=edge[a[i]].to;
    int v2=edge[a[i]^1].to;
    if(dis[v]*2<dis[l] && 2*dis[v2]>=dis[l]) mid=v2;
  }
  memset(vis,0,sizeof vis); num1=0;
}
//求直径的子节点
void dfs_center(int u){
  vis[u]=1;
  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]=0;
}

int DP(int u){
  vis[u]=1;
  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]=0;
  if(!dp[u]) dp[u]=1<<30;//注意这里
  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=0;
    for(int i=1;i<=num1;i++) {int tmp=DP(a[i]);mx=max(mx,tmp),ans+=tmp;}
    if(num1>1) ans-=mx;
  }
  else{
    if(dis[l]-dis[mid]>dis[mid]){//这是左边比右边长
      for(int i=1;i<=num1;i++) ans+=DP(a[i]);
      if(ans!=0) ans=min(ans,DP(b[1]));
      else ans=DP(b[1]);
    }
    else{
      for(int i=1;i<=num2;i++) ans+=DP(b[i]);
      if(ans!=0) ans=min(ans,DP(a[1]));
      else ans=DP(a[1]);
    }
  }
}

int main(){
  n=rd();
  for(int i=1;i<n;i++){
    int x=rd(),y=rd(),z=rd(),p=rd();
    add(x,y,z,p); add(y,x,z,p);
  }
  dfs(1);
  init();
  dfs(l);//求出直径以及两个端点
  get_mid();//求出中点
  solve();
  printf("%d\n",ans);
  return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值