NOIP模拟题(图论)两道

5 篇文章 0 订阅
2 篇文章 0 订阅

T1:树的直径

【题目描述】
⼀一棵树的直径指的是树中距离最远的两点间的距离。给出一颗无根树,请你求出它的直径。
【输入】
输入的第1包含1个整数n,表示树中节点个数。
接下来的n-1,每行有3个整数u,v,w。表示存在一条从u到v,长度为w的边。
【输出】
输出一行,包含1个整数,即树的直径。
【输入输出样例】

INPUT:                         OUTPUT

6                               13
1 2 3
1 3 4
2 4 2
4 6 3
2 5 6


【输入输出样例解释】
直径为 3->1->2->5 ,总长度为13。
对于30%的数据,有2≤N100。
对于50%的数据,有2≤N≤3000
对于70%的数据,有2≤N100,000。
对于100%的数据,有2≤N≤500,000,1≤u,v≤N,1≤w≤10000。 


T2:羊驼村庄

【问题描述】

在宽广的马勒戈壁上有n个村庄和n - 1条无向道路,村庄之间两两可达,道路有长度。给你马勒戈壁的地图,统计这样一个信息:对于每个村庄,离它最远的那个村庄有多远?

【输入格式】

第一行包含一个正整数n,表示村庄数。

以下n – 1行每行包含x, y, z三个整数表示村庄x至村庄y存在一条长度为z的道路。

【输出格式】

输出包含n行,第i行输出离村庄i最远村庄的距离。

【样例输入1】

3

1 2 1

1 3 2

【样例输出1】

2

3

3

【样例输入2】

7

1 2 1

2 3 1

3 4 1

4 5 1

5 6 1

6 7 1

【样例输出2】

6

5

4

3

4

5

6

【数据规模和约定】

对于30%的数据,n <= 2000;

对于60%的数据,n <= 20000;

对于100%的数据,1 <= n <= 200000,道路长度非负,且所有道路的长度之和不大于109



---------------------------------------------------------------------------------------------

----------------我是分割线-----------初次看题的可以先考虑一下----------------------------------

---------------------------------------------------------------------------------------------

TI题解:

算法一:

暴力求每两点间距离,找出最大值。
floyd算法
O(n3)
期望得分:30
算法二:
这是棵树诶。
枚举每个点进行BFS/DFS,求出最大值。
O(n2)
期望得分:50
这50分是送的
算法三:
对于500000的数据,考虑O(n)的方法。
任取一个点(记作A),BFS/DFS找到距离最远的点(记作B)。
从B点出发,找到距离B最远的点(记作C)。
则BC即是树的直径。
期望得分:100
记住该做法!!!
代码:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<queue>
using namespace std;
int n,now(0),ans(0),bj(0);
queue<int>Q;
struct edge
{
	int u,v,z;
	edge *next;
}e[500005*2],*p=e,*point[500005*2];
void addedge(int x,int y,int z)
{
	p++;p->u=y;p->v=z;p->next=point[x];
	point[x]=p;
}
int dis[500005],used[500005];
inline void spfa(int x)//这里没用bfs/dfs,而是用了SPFA,虽然慢,但题解的dfs也超了两个点。。。
{
	memset(dis,0x7f,sizeof(dis));
	memset(used,0,sizeof(used));
	Q.push(x);
	used[x]=1;
	dis[x]=0;
	while(!Q.empty())
	{
		x=Q.front();Q.pop();used[x]=0;
		for(p=point[x];p;p=p->next)
		{
			if(dis[p->u]>dis[x]+p->v)
			{
				dis[p->u]=dis[x]+p->v;
				if(!used[p->u])used[p->u]=1,Q.push(p->u);
			}
		}
	}
}
int main()
{
	freopen("diameter.in","r",stdin);
	freopen("diameter.ans","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<n;i++)
	{
		int x,y,z;
		scanf("%d %d %d",&x,&y,&z);
		addedge(x,y,z);
		addedge(y,x,z);
	}
	spfa(1);//随便找一个点A
	for(int i=1;i<=n;i++)
	{
		if(dis[i]>now&&dis[i]!=dis[0])
		{
			now=dis[i];
			bj=i;//找出与A距离最大的点
		}
	}
	spfa(bj);
	for(int i=1;i<=n;i++)
	{
		if(dis[i]>ans&&dis[i]!=dis[0])
		{
			ans=dis[i];
		}
	}
	printf("%d\n",ans);
	return 0;
}
这道题主要是考思维。算法谁都会,就看思路对不对。这道题让我在小伙伴面前ZB,爽!


T2题解:
算法一:DP,比较麻烦,只给出简单思路说明:
¥状态定义:
¥记fd[i][0]表示以i为起点,向下的最长路径长度
¥Fd[i][1]以i为起点,向下的次长路径长度
¥G[i][0]表示fd[i][0]在哪个儿子处取到
¥G[i][1]表示fd[i][1]在哪个儿子处取到
¥递推方程:
¥设当前枚举到点i,i的父亲为t,则:
¥If (g[t][0] != i)
  Fu[i] = max(fu[t], fd[t][0]) + w[i][t]
¥Else
¥  fu[i] = max(fu[t], fd[t][1]) + w[i][t]
¥设当前枚举到点i
¥枚举i的所有儿子j
¥If (fd[j][0] + w[i][j] > fd[i][0]) {
  fd[i][1] = fd[i][0];
  g[i][1] = g[i][0];
  fd[i][0] = fd[j][0] + w[i][j];
  g[i][0] = j;
} else if (fd[j][0] + w[i][j] > fd[i][1]) {
  fd[i][1] = fd[j][0] + w[i][j];
  g[i][1] = j;
}
¥对于一个点i,它既要用到父亲的信息也要用到儿子的信息
¥所以需要两次dfs
¥第一次dfs自下而上计算出所有点的fd值
¥第二次dfs自上而下计算出所有点的fu值
¥总时间复杂度O(n)
这个是题解的DP,我没有用DP做,所以说不大清,又想要的可以在底下评论里说说,我会把ppt发给你的~
算法二:3遍SPFA。
·先设一个点为A,跑一边SPFA(A),找出距离最大的点B
·再跑一边SPFA(B),找出与B距离最远的点C
·再跑一遍SPFA(C),答案就是max(disB[i],disC[i])!.
上代码:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<queue>
using namespace std;
int n,now(0),bj(0),f[200005],maxx(0);
queue<int>Q;
struct edge
{
	int u,v;
	edge *next;
}e[200005*2],*p=e,*point[200005*2];
void addedge(int x,int y,int z)
{
	p++;p->u=y;p->v=z;p->next=point[x];
	point[x]=p;
}
int used[200005],dis[200005];
void SPFA(int x)
{
	memset(used,0,sizeof(used));
	memset(dis,0x7f,sizeof(dis));
	Q.push(x);
	used[x]=1;
	dis[x]=0;
	while(!Q.empty())
	{
		x=Q.front();Q.pop();used[x]=0;
		for(edge *j=point[x];j;j=j->next)
		{
			if(dis[j->u]>dis[x]+j->v)
			{
				dis[j->u]=dis[x]+j->v;
				if(!used[j->u])used[j->u]=1,Q.push(j->u);
			}
		}
	}
}
int main()
{
	freopen("village.in","r",stdin);
	freopen("village.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<n;i++)
	{
		int x,y,z;
		scanf("%d %d %d",&x,&y,&z);
		addedge(x,y,z);
		addedge(y,x,z);
	}
	SPFA(1);
	for(int i=1;i<=n;i++)
	{
		if(dis[i]>now)
		{
			now=dis[i];
			bj=i;
		}
	}
	SPFA(bj);
	for(int i=1;i<=n;i++)
	{
		f[i]=dis[i];//找个数组先记下来SPFA(B)
	}
	for(int i=1;i<=n;i++)
	{
	    if(dis[i]>maxx)
		{
			maxx=dis[i];
			bj=i;
		}	
	}
	SPFA(bj);
	for(int i=1;i<=n;i++)
	{
		f[i]=max(f[i],dis[i]);
	}
	for(int i=1;i<=n;i++)
	printf("%d\n",f[i]);
	return 0;
}

这个题让我又装了一次!嘿嘿嘿。。。



总结:这两道题反映出了我水平的提升,但离我的目标还有一定的差距,我还需要努力!






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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值