[TJOI2017]城市 树形dp+树的直径+树的中心

5 篇文章 0 订阅

原题:https://www.luogu.org/problemnew/show/P3761

题解:修改一条边,使最长链最短。枚举每一条边,显然可以将树分成两个联通块,那么最长链可以是两个联通块的直径,也可以是,连接两个联通块的中心,即两颗树的半径+枚举的边长。可以用树形dp求。

对于直径:

  • 设dp[x][0/1]表示以x为根子树的最大长,和次大长
void getd(int x,int &ans){
	
	for(int i=h[x];i;i=data[i].nxt){
		int y=data[i].to;int c=data[i].w;if(vis[y]) continue;
		vis[y]=1;getd(y,ans);
		if(dp[y][0]+c>dp[x][0]){//最大链 
			dp[x][1]=dp[x][0];son[x]=y;
			dp[x][0]=dp[y][0]+c;
		}else if(dp[y][0]+c>dp[x][1]){//次大链
			dp[x][1]=dp[y][0]+c;
		} 
	} 
	ans=max(ans,dp[x][0]+dp[x][1]);
}

 对于中心和半径有:

  • from[x]表示除以x为根的子树外,其他点到x的最长距离
  • 从根顺着推
void getr(int x,int from,int &ans){
	ans=min(ans,max(dp[x][0],from));
	for(int i=h[x];i;i=data[i].nxt){
		int y=data[i].to;int c=data[i].w;
		if(!vis[y]) continue;
		vis[y]=0;
		if(son[x]==y) getr(y,max(dp[x][1],from)+c,ans);
		else getr(y,max(dp[x][0],from)+c,ans);
	}
}
#include<bits/stdc++.h>
#define inf (1<<30)-1
using namespace std;
const int N=5500;
int dp[N][2];
int n,len=1;
struct E{
	int to,w,nxt;
}data[N<<1];
int u[N],v[N],val[N],h[N],son[N];
bool vis[N];
int d1,d2,r1,r2,ans;
inline int rd(){
	int x=0;int f=1;char s=getchar();
	while(!isdigit(s)) f=(s=='-'?-1:f),s=getchar();
	while(isdigit(s)) x=(x<<1)+(x<<3)+s-'0',s=getchar();
	return x*f;
}
inline void ins(int x,int y,int w){
	data[++len].to=y;data[len].w=w;data[len].nxt=h[x];h[x]=len;
	data[++len].to=x;data[len].w=w;data[len].nxt=h[y];h[y]=len;
}
inline void clear(){
	memset(dp,0,sizeof dp);
	memset(vis,0,sizeof vis); 
	memset(son,0,sizeof son);
} 

void getd(int x,int &ans){
	
	for(int i=h[x];i;i=data[i].nxt){
		int y=data[i].to;int c=data[i].w;if(vis[y]) continue;
		vis[y]=1;getd(y,ans);
		if(dp[y][0]+c>dp[x][0]){//最大链 
			dp[x][1]=dp[x][0];son[x]=y;
			dp[x][0]=dp[y][0]+c;
		}else if(dp[y][0]+c>dp[x][1]){
			dp[x][1]=dp[y][0]+c;
		} 
	} 
	ans=max(ans,dp[x][0]+dp[x][1]);
}
void getr(int x,int from,int &ans){
	ans=min(ans,max(dp[x][0],from));
	for(int i=h[x];i;i=data[i].nxt){
		int y=data[i].to;int c=data[i].w;
		if(!vis[y]) continue;
		vis[y]=0;
		if(son[x]==y) getr(y,max(dp[x][1],from)+c,ans);
		else getr(y,max(dp[x][0],from)+c,ans);
	}
}
int main(){
	//freopen("city.in","r",stdin);
	n=rd();
	for(int i=1;i<n;i++){
		int x=u[i]=rd();int y=v[i]=rd();int w=val[i]=rd();
		ins(x,y,w);
	}
	ans=inf;
	for(int i=1;i<n;i++){
		clear();
		vis[v[i]]=1;
		d1=0;getd(u[i],d1);
		d2=0;getd(v[i],d2);
		vis[v[i]]=0;
		r1=inf;getr(u[i],0,r1);
		r2=inf;getr(v[i],0,r2);
		ans=min(ans,max(max(d1,d2),r1+r2+val[i]));
	}
	printf("%d\n",ans);

	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值