P3304 [SDOI2013]直径(【模板】树直径的必经边)

题目地址



基本思路:

  • 题目要求树直径的必经边,那么首先应当获取一条直径.
  • 获取直径后从直径上的两个端点分别遍历一次直径,每次遍历直径时从直径上的每个点分别dfs一次并不经过直径上的点,如果深度可以被替换则说明非必经边.

#include<cstdio>
#include<iostream>
#include<queue>
#include<cstring>
#define ll long long
using namespace std;
const int MAXN=2e5+10,MAXM=MAXN*2;
struct Edge{
	ll from,to,w,nxt;
}e[MAXM];
int head[MAXN],edgeCnt=1;
void addEdge(ll u,ll v,ll w){
	e[++edgeCnt].from=u;
	e[edgeCnt].to=v;
	e[edgeCnt].w=w;
	e[edgeCnt].nxt=head[u];
	head[u]=edgeCnt;
}
ll dep[MAXN];
bool vis[MAXN];
int n;
int from[MAXN];
bool inDiameter[MAXN];//是否在直径上
ll st,ed;//直径端点
void markDiameter(){//标记直径
	int tmp=ed;
	while(tmp){
		inDiameter[tmp]=1;
		tmp=from[tmp];
	}
}
ll bfs(int s){//求直径
	memset(dep,0,sizeof(dep));
	memset(vis,0,sizeof(vis));
	memset(from,0,sizeof(from));
	queue<int> q;
	q.push(s);
	vis[s]=1;
	while(!q.empty()){
		int nowU=q.front();
		q.pop();
		for(int i=head[nowU];i;i=e[i].nxt){
			int nowV=e[i].to;
			if(!vis[nowV]){
				dep[nowV]=dep[nowU]+e[i].w;
				vis[nowV]=1;
				from[nowV]=nowU;
				q.push(nowV);
			}
		}
	}
	ll ans=0,ansDep=0;
	for(int i=1;i<=n;i++){
		if(dep[i]>ansDep){
			ans=i,ansDep=dep[i];
		}
	}
	return ans;
}
ll dis_fromST[MAXN];//每个点到直径st端点的距离
bool vis_getDisFromST[MAXN];
void bfs_getDisFromST(){
	queue<int> q;
	q.push(st);
	vis_getDisFromST[st]=1;
	while(!q.empty()){
		int nowU=q.front();
		q.pop();
		for(int i=head[nowU];i;i=e[i].nxt){
			int nowV=e[i].to;
			if(!vis_getDisFromST[nowV]){
				vis_getDisFromST[nowV]=1;
				dis_fromST[nowV]=dis_fromST[nowU]+e[i].w;
				q.push(nowV);
			}
		}
	}
}
ll dis_fromED[MAXN];//每个点到直径ed端点的距离
bool vis_getDisFromED[MAXN];
void bfs_getDisFromED(){
	queue<int> q;
	q.push(ed);
	vis_getDisFromED[ed]=1;
	while(!q.empty()){
		int nowU=q.front();
		q.pop();
		for(int i=head[nowU];i;i=e[i].nxt){
			int nowV=e[i].to;
			if(!vis_getDisFromED[nowV]){
				vis_getDisFromED[nowV]=1;
				dis_fromED[nowV]=dis_fromED[nowU]+e[i].w;
				q.push(nowV);
			}
		}
	}
}
ll dep_noDiameter[MAXN];//不经过直径的最大深度
ll dfs_noDiameter(int x,int fa){
	ll maxDep=dep_noDiameter[x];
	for(int i=head[x];i;i=e[i].nxt){
		int nowV=e[i].to;
		if(nowV==fa||inDiameter[nowV])continue;
		dep_noDiameter[nowV]=dep_noDiameter[x]+e[i].w;
		ll tmp=dfs_noDiameter(nowV,x);
		maxDep=max(maxDep,tmp);
	}
	return maxDep;
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n-1;i++){
		ll a,b,c;
		cin>>a>>b>>c;
		addEdge(a,b,c);
		addEdge(b,a,c);
	}
	st=bfs(1);
	ed=bfs(st);//直径
	cout<<dep[ed]<<endl;
	markDiameter();//标记直径上点
	bfs_getDisFromST();
	bfs_getDisFromED();//获取每个点到两个端点的距离
	int l=st,r=ed;//必经边端点
	int tmp=from[ed];
	while(tmp){//r
		if(tmp==st)break;
		dep_noDiameter[tmp]=0;
		ll nowMaxDep=dfs_noDiameter(tmp,0);
		if(nowMaxDep==dis_fromED[tmp]){
			r=tmp;
		}
		tmp=from[tmp];
	}
	bfs(ed);//重新获取from数组
	tmp=from[st];
	while(tmp){//l
		if(tmp==ed||tmp==r)break;
		ll nowMaxDep=dfs_noDiameter(tmp,0);
		if(nowMaxDep==dis_fromST[tmp])l=tmp;
		tmp=from[tmp];
	}
	int cnt=0;
	tmp=l;
	while(tmp){
		if(tmp==r)break;
		cnt++;
		tmp=from[tmp];
	}
	printf("%d\n",cnt);
	return 0;
}

  

原文地址:https://www.cnblogs.com/zbsy-wwx/p/11746614.html

时间: 2025-01-14 02:49:21

P3304 [SDOI2013]直径(【模板】树直径的必经边)的相关文章

SDOI2013 直径(树的直径必经边)

SDOI2013 直径 题目传送 sol: 先求出任一直径同时把直径拎出来,树的非直径部分全部挂在直径上(如下). 对于直径上的每一个点i,如果存在它到非直径上点的最大距离\(g[i]\)等于它到直径两端点中较短的那一段\(d[i]\), 则说明这一段也可以成为直径中的一部分. 而我们需要得到所有直径的交,画图可以发现假设两端(以中点为界)都存在上述的点,最逼近的两点间的边即为所求! 具体可以看代码实现. code: #include<bits/stdc++.h> #define IL inl

拓扑排序,树的直径模板(CF14D 枚举删边)

HDU4607 树的直径 #include <stdio.h> #include <string.h> #include <iostream> #include <queue> #include <vector> using namespace std; #define N 100005 #define INF 1<<30 int n,dis[N],E; bool vis[N]; vector<int>G[N]; //注意

树的直径、树的重心与树的点分治

树的直径 树的直径(Diameter)是指树上的最长简单路. 直径的求法:两遍搜索 (BFS or DFS) 任选一点w为起点,对树进行搜索,找出离w最远的点u. 以u为起点,再进行搜索,找出离u最远的点v.则u到v的路径长度即为树的直径. 简单证明: 如果w在直径上,那么u一定是直径的一个端点.反证:若u不是端点,则从直径另一端点到w再到u的距离比直径更长,与假设矛盾. 如果w不在直径上,且w到其距最远点u的路径与直径一定有一交点c,那么由上一个证明可知,u是直径的一个端点. 如果w到最远点u

BZOJ 1912 巡逻(树直径)

题目链接:http://61.187.179.132/JudgeOnline/problem.php?id=1912 题意:给出一棵树,边权为1.现在加一条或两条边后,使得从1出发遍历每个点至少一次再回到1的路程最短. 思路:先求一次树的直径Max1.然后将直径的边权改为-1,再求一次直径Max2.答案为ans=(n-1)*2-(Max1-1)-(Max2-1). struct node { int u,v,w,next; }; node edges[N<<1]; int head[N],e;

bzoj 1912 巡逻(树直径)

Description Input 第一行包含两个整数 n, K(1 ≤ K ≤ 2).接下来 n – 1行,每行两个整数 a, b, 表示村庄a与b之间有一条道路(1 ≤ a, b ≤ n). Output 输出一个整数,表示新建了K 条道路后能达到的最小巡逻距离. Sample Input 8 1 1 2 3 1 3 4 5 3 7 5 8 5 5 6 Sample Output 11 HINT 10%的数据中,n ≤ 1000, K = 1: 30%的数据中,K = 1: 80%的数据中,

[HDOJ4612]Warm up(双连通分量,缩点,树直径)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4612 所有图论题都要往树上考虑 题意:给一张图,仅允许添加一条边,问能干掉的最多条桥有多少. 必须解决重边的问题,最后会说. 首先tarjan跑出所有的双连通分量和是桥的边还有桥的数量,这非常重要.接着缩点重新建图,然后两遍dfs找出两个在树上距离最远的点.我的想法就是把这条最长的链连成一个环,让它成为一个双连通分量,这样的效果是最好的.最后就是用桥的数量减去树直径再减一就得到了剩下的桥的数量了.求

(树直径) bzoj 1509

1509: [NOI2003]逃学的小孩 Time Limit: 5 Sec  Memory Limit: 64 MBSubmit: 447  Solved: 240[Submit][Status][Discuss] Description Input 第一行是两个整数N(3 ? N ? 200000)和M,分别表示居住点总数和街道总数.以下M行,每行给出一条街道的信息.第i+1行包含整数Ui.Vi.Ti(1?Ui, Vi ? N,1 ? Ti ? 1000000000),表示街道i连接居住点U

(边双联通+树直径) hdu 4612

Warm up Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65535/65535 K (Java/Others)Total Submission(s): 4437    Accepted Submission(s): 1001 Problem Description N planets are connected by M bidirectional channels that allow instant transport

[HDOJ2196]Computer (树直径 树形DP)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2196 给一棵树,求树上各点到某点的距离中最长的距离.注意每个点都要求. 和普通求树的直径不一样,要求每一个点,点的数量是10000因此每一个点都跑一次dfs就会超时.因此先随便dfs一个点,找到一个点后以此点为原点再找距离它最远的点,再找一次即可找到树直径两端的点. 1 #include <algorithm> 2 #include <iostream> 3 #include <