Bzoj3124 [SDO2013]直径

今天突然发现树形dp鬼都不会了……

于是果断找题来做

发现还是码力太弱QAQ

第一题Bzoj1040骑士,有思路但是死活写不对,于是膜了一发神犇的代码

第二题就是这个题:3124: [Sdoi2013]直径

嘿嘿嘿连思路都没有了QAQ

一遇到这样繁琐的题目我就乱糟糟

于是又膜了一发……

只能说我还是太naive

对于这道题,我们可以开这么几个数组:

f[i][j]:从i开始走到i为根的子树的一个叶节点的第j长路径的长度

ff[i][j]:对应f[i][j]表示这个长度是从哪个点转移来的,这个点是直接相连的点

h[i][j]:再i的子树中直径第j大的是多少,注意是子树中,即这个点不算

hf[i][j]:同理是从那里转移过来的

g[i]:以i为根的子树的直径

以上信息是一边树形dp可以直接做出来的

重点是我们怎么统计答案

我们发现一条路径是关键边即所有直径必须经过的边当且仅当删去他后分成的两棵子树的直径的最大值变小了

反证法很容易发现这一点

所以我们要统计删除每条边后的最大直径

我们发现树形dp做出来的5个数组都是关于子树的,且都是固定整棵树的根下的子树

而我们统计每一条边的时候用到的不只是子树,还有整棵树删去这棵子树和当前边后的另一半

我们可以维护这被分开的另一棵树中的到当前考虑边的深度较浅的点最长链

我们转移到当前考虑边,相当于为这条链开辟了新路径,如图


假设我们正在转移蓝边,且我们刚刚转移完了红边,另一半子树的最长链是红边+黄边

那么我们转移到蓝边的时候,对于另一棵子树,绿边也就可以成为更新他的直径的一部分

当然一个点可能有很多条边,另一棵子树的直径情况多种多样,也可能在边所对应的一坨子树中,也可能并不经过当前考虑的点

所以这三种情况我们要综合考虑一下即可得到另一棵子树的直径,因此我们还得记录一下另一棵子树转移前的直径

对于蓝边对应子树的直径,不就是g[]吗QAQ

所以下面程序中dfs()里的参数:len表示通向当前点的最长链,maxl表示转移前另一棵子树的直径

代码:

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define LL long long
using namespace std;
const int maxn=200000+10;
int n,m,tot=0,ff[maxn][3],hf[maxn][3];
LL f[maxn][3],h[maxn][3],g[maxn],dia;
struct edge{int to,next,w;}G[maxn*5];
int head[maxn],ans=0;

void add(int x,int y,int z){
	G[++tot].to=y; G[tot].next=head[x]; head[x]=tot; G[tot].w=z;
}

void allinit(int x){
	for (int i=0;i<3;++i) f[x][i]=0;
	for (int i=0;i<2;++i) h[x][i]=0;
	g[x]=0;
}

void treedp(int x,int fa){
	allinit(x);
	for (int i=head[x];i;i=G[i].next){
		int v=G[i].to;
		if (v==fa) continue;
		treedp(v,x);
		int w=G[i].w;
		if (f[v][0]+w>=f[x][0]){
			f[x][2]=f[x][1]; f[x][1]=f[x][0];
			f[x][0]=f[v][0]+w; ff[x][2]=ff[x][1];
			ff[x][1]=ff[x][0]; ff[x][0]=v;
		}else if (f[v][0]+w>=f[x][1]){
			f[x][2]=f[x][1]; ff[x][2]=ff[x][1];
			f[x][1]=f[v][0]+w; ff[x][1]=v;
		}else if (f[v][0]+w>f[x][2])
			f[x][2]=f[v][0]+w,ff[x][2]=v;
		g[x]=max(g[x],g[v]);
		if (g[v]>h[x][0]){
			h[x][1]=h[x][0]; hf[x][1]=hf[x][0];
			h[x][0]=g[v]; hf[x][0]=v;
		}else if (g[v]>h[x][1])
		    h[x][1]=g[v],hf[x][1]=v;
	} g[x]=max(g[x],f[x][0]+f[x][1]);
}

void dfs(int u,int fa,LL len,LL maxl){
	for (int i=head[u];i;i=G[i].next){
		int v=G[i].to;
		if (v==fa) continue;
		LL len1=g[v],len2,len3;
		if (v==hf[u][0]) len2=h[u][1];
		else len2=h[u][0];
		len2=max(len2,maxl);
		if (v==ff[u][0]) len2=max(len2,f[u][2]+f[u][1]);
		else if (v==ff[u][1]) len2=max(len2,f[u][0]+f[u][2]);
		else len2=max(f[u][0]+f[u][1],len2);
		if(v==ff[u][0]) len3=f[u][1];
        else len3=f[u][0];
        len2=max(len2,len+len3);
        if(max(len1,len2)<dia) ans++;
        dfs(v,u,max(len3,len)+G[i].w,len2);
	}
}

int main(){
	scanf("%d",&n);
	for (int i=1;i<n;++i){
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		add(x,y,z); add(y,x,z);
	}
	treedp(1,0);
	dia=g[1]; ans=0;
	dfs(1,0,0,0);
	printf("%lld\n%d",dia,ans);
}


  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值