[NOI2007]社交网络(Floyd最短路计数)

写在前面:

这一次写题,我觉得我前面的工作做得还是可以的。

  1. 已经可以把题目原本的意思自己提取出来了(之前更多的靠的是有题解提取题目或者是有的题老师的PPT上会直接有);
  2. 知道了这道题考的是哪一个知识点(虽然我不会,但是我知道要向哪一个方向想,虽然方向还不是很精准)。

但是同样地这也一定的暴露出来了我的一些问题:

  1. 之前学过的知识点掌握的并不牢固,没有理解其重要思想(比如Floyd是怎么在运行的),没有学透彻;
  2. 思考的时间不仅太长而且码代码很慢,这就导致了自己刷题刷的很慢(我觉得计算机没有题的积累是很难学得更好的)

下一次可以考虑自己在打代码的时候更专心一点,争取让自己的代码少一些漏洞。


题面

题目传送门

在社交网络 ( Social Network ) 的研究中,我们常常使用图论概念去解释一些社会现象。不妨看这样的一个问题:
在一个社交圈子里有 n n n 个人,人与人之间有不同程度的关系。我们将这个关系网络对应到一个 n n n 个结点的无向图上,两个不同的人若互相认识,则在他们对应的结点之间连接一条无向边,并附上一个正数权值 c c c c c c 越小,表示两个人之间的关系越密切。我们可以用对应结点之间的最短路长度来衡量两个人 s s s t t t 之间的关系密切程度,注意到最短路径上的其他结点为 s s s t t t 的联系提供了某种便利,即这些结点对于 s s s t t t 之间的联系有一定的重要程度。我们可以通过统计经过一个结点 v v v 的最短路径的数目来衡量该结点在社交网络中的重要程度。考虑到两个结点 A A A B B B 之间可能会有多条最短路径。我们修改重要程度的定义如下: C s , t C_{s,t} Cs,t 表示从s到t的不同的最短路的数目, C s , t ( v ) C_{s,t}(v) Cs,t(v) 表示经过 v v v s s s t t t 的最短路的数目;则定义:
I ( v ) = ∑ s ≠ v , t ≠ v C s , t ( v ) C s , t I(v)=\sum_{s \ne v,t\ne v} \frac{C_{s,t}(v)}{C_{s,t}} I(v)=s̸=v,t̸=vCs,tCs,t(v)
为结点 v v v 在社交网络中的重要程度。为了使 I ( v ) I(v) I(v) C s , t ( v ) C_{s,t}(v) Cs,t(v) 有意义,我们规定需要处理的社交网络都是连通的无向图,即任意两个结点之间都有一条有限长度的最短路径。现在给出这样一幅描述社交网络的加权无向图,请你求出每一个结点的重要程度。
输入格式
输入第一行有两个整数 n n n m m m ,表示社交网络中结点和无向边的数目。
在无向图中,我们将所有结点从 1 1 1 n n n 进行编号。
接下来 m m m 行,每行用三个整数 a , b , c a , b , c a,b,c 描述一条连接结点 a a a b b b ,权值为 c c c 的无向边。
注意任意两个结点之间最多有一条无向边相连,无向图中也不会出现自环(即不存在一条无向边的两个端点是相同的结点)。
输出格式
输出包括 n n n 行,每行一个实数,精确到小数点后 3 3 3 位。第 i i i 行的实数表示结点 i i i 在社交网络中的重要程度。
输入输出样例
输入样例
#1
4 4
1 2 1
2 3 1
3 4 1
4 1 1
输出样例
#1
1.000
1.000
1.000
1.000
说明 :
对于1号结点而言,只有2号到4号结点和4号到2号结点的最短路经过1号结点,而2号结点和4号结点之间的最短路又有2条。因而根据定义,1号结点的重要程度计算为1/2+1/2=1。由于图的对称性,其他三个结点的重要程度也都是1。
对于 50 % 50\% 50% 的数据, n ≤ 10 , m ≤ 45 n \le 10 , m \le 45 n10,m45
对于 100 % 100\% 100% 的数据, n ≤ 100 , m ≤ 4500 n \le 100 , m \le 4500 n100,m4500 ,任意一条边的权值 c c c 是正整数且 1 ⩽ c ⩽ 1000 1 \leqslant c \leqslant 1000 1c1000
所有数据中保证给出的无向图连通,且任意两个结点之间的最短路径数目不超过 1 0 10 10^{10} 1010


思路

  1. 简化题意:无向图中n个点,m条边,求每个点 k 所经过的不同于自己的任意两个点(s,t)的最短路的条数 / / /那两个点( s , t )的一共的最短路的条数 的累加和;
  2. 由上面的题意我们可以知道该题难点就是让我们求出两个点之间的最短路的条数;

那么问题就进一步的变成了如何求两个点之间的最短路的条数以及如果求必定经过一个点k的最短路的条数
如果当前求出了s->k的最短路的条数(cnt(s,k))以及k->t 的最短路的条数(cnt(k,t)),那么s->t 的被k经过的最短路的条数(cnt(s,t))就应该是两者相乘(根据乘法原理可知)

因为点n最多只有100个,所以这道题的优先策略是用Floyd求最短路数

那么问题来了,如何使用Floyd求两点之间的最短路数??

博主一开始认为Floyd是不可以求最短路数的,陷入了一个思维错误的怪圈
之后会在文章结尾补充上为什么可以使用Floyd求最短路计数。

根据上面的分析,我们可以得知:可以使用乘法原理求出经过一点的两点之间的最短路条数;
这样子的话,我们若知道了两点(i,j)之间所有的经过点,以及经过的点(k)到达起点和终点的最短路数,把所有的经过点的最短路数累加即可得到结果cnt(i,j)。

c n t i , j cnt_{i,j} cnti,j表示i到j的最短路的数量(即 C i , j C_{i,j} Ci,j), d i s i , j dis_{i,j} disi,j表示i到j的最短路长度;

c n t i , j = c n t i , k ∗ c n t k , j cnt_{i,j}=cnt_{i,k}*cnt_{k,j} cnti,j=cnti,kcntk,j (当 d i s i , j > d i s i , k + d i s k , j dis_{i,j}>dis_{i,k}+dis_{k,j} disi,j>disi,k+disk,j时)
c n t i , j + = c n t i , k ∗ c n t k , j cnt_{i,j}+=cnt_{i,k}*cnt_{k,j} cnti,j+=cnti,kcntk,j(当 d i s i , j = = d i s i , k + d i s k , j dis_{i,j}==dis_{i,k}+dis_{k,j} disi,j==disi,k+disk,j时)

初始时,对于每两个有边直接相连的点i和j, c n t i , j = 1 cnt_{i,j}=1 cnti,j=1
由上可知,经过k的最短路条数为s到k的最短路条数乘k到t的最短路条数,即 C s , t ( k ) = C s , k ∗ C k , t C_{s,t}(k)=C_{s,k}*C_{k,t} Cs,t(k)=Cs,kCk,t.

#include<bits/stdc++.h>
using namespace std;

const int nn=103;
typedef long long ll;

int n,m;
int a[nn][nn];
ll cnt[nn][nn];

inline int read()
{
	int x=0,f=1; char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();}
	return x*f;
} 

int main()
{
	n=read(), m=read();
	memset(a,0x3f3f3f,sizeof(a));
	memset(cnt,0x3f3f3f,sizeof(cnt));
	for(int i=1;i<=m;++i)
	{
		int x=read(), y=read(), z=read();
		a[x][y]=a[y][x]=min(a[x][y],z);
		cnt[x][y]=cnt[y][x]=1;/*最短路初始化的时候都为1,因为此时输入的数据在建成图或树且没有更新之前两两之间只有一条边相连*/
	}
	
	for(int k=1;k<=n;++k)
		for(int i=1;i<=n;++i)
			for(int j=i+1;j<=n;++j)
			{
				if(a[i][j]==a[i][k]+a[k][j])	cnt[i][j]=cnt[j][i]=cnt[i][j]+cnt[i][k]*cnt[k][j];
				if(a[i][j]>a[i][k]+a[k][j])
				{
					a[i][j]=a[j][i]=a[i][k]+a[k][j];
					cnt[i][j]=cnt[j][i]=cnt[i][k]*cnt[k][j];/*更新,重新定义两点之间的最短路数*/
				}
			}
	for(int k=1;k<=n;++k)
	{
		double ans=0;
		for(int i=1;i<=n;++i)
			for(int j=1;j<=n;++j)/*这里重复也是会计算上价值的*/
				if(a[i][j]==a[i][k]+a[k][j])
					ans+=cnt[i][k]*cnt[k][j]*1.0/cnt[i][j];/*博客上有解释*/
		printf("%.3lf\n",ans);
	}
	return 0;
}

结尾的补充

上面说到博主一开始是觉得Floyd求最短路数是不对的,因为我觉得可能会有重复的边被计算上,这样的话求得最短路的条数是不对的,所以我在这道题中排除了这一个做法,之后便想学一下Dijkstra求最短路计数(虽然到现在都没有实现 ),再之后就翻看了题解,发现Floyd是可以最短路计数的。

然后,我就开始考虑为什么Floyd是可以进行最短路计数的?

我始终没有想出来 )最后是startaidou大佬和Chdy大佬的告知才让我些许通透了些。

  • 思考一下Floyd是如何运行的呢?

应该是枚举每一个可以作为中间点的点进行两点之间的更新。也就是说,Floyd在更新的时候始终是只有三个点在操作。

  • 为什么自己会认为会有重复的边被计算上?

因为我认为若是s->i->j->t这一条路径可能会分别被i和j都计算到s和t的最短路数中,这样子的话这一条路径就会被计算了两次。

  • 考虑一下我是什么出了问题?

没有仔细的想一下Floyd的运行过程,因为Floyd始终都是三个点再进行操作,所以在统计的过程中根本就不会由上面的四个点的出现。而是s->i->j,i->j->t。可能有人会说这不是一条路径吗?但是再仔细看一下,这一条路径的两个起始点根本就不一样,所以就算是一条路径造成的也是分别的两对不同的点对的贡献。这样子的话就不会出现路径重复的问题了。


内心不渴望的东西,不可能靠近自己。

完……

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值