[JSOI2007]重要的城市(Floyd最短路计数)

写在前面

这道题也是我提交了好几遍的题,并不是说它有多么多么的重要,而是自己一直都会出错,所以为了使得自己不出错,就写了好几遍。

但是天天写其实也一点意义都没有,应该向焦老师(我的信奥老师)说的那样,做的题,今天写了之后,明天再写一遍,之后三天写一遍,再之后五天写一遍,再之后一个月写一遍。


题目描述

题目传送门

题目描述
参加jsoi冬令营的同学最近发现,由于南航校内修路截断了原来通向计算中心的路,导致去的路程比原先增加了近一公里。而食堂门前施工虽然也截断了原来通向计算中心的路,却没有使路程增加,因为可以找到同样长度的路作替代。其实,问题的关键在于,路截断的地方是交通要点
同样的情况也出现在城市间的交通中。某些城市如果出了问题,可能会引起其他很多城市的交通不便。另一些城市则影响不到别的城市的交通。jsoi冬令营的同学发现这是一个有趣的问题,于是决定研究这个问题。
他们认为这样的城市是重要的:如果一个城市c被破坏后,存在两个不同的城市a和b(a, b均不等于c),a到b的最短距离增长了(或不通),则城市c是重要的。
jsoi冬令营的同学面对着一张教练组交给他们的城市间交通图,他们希望能找出所有重要的城市。现在就请你来解决这个问题。
输入输出格式
输入格式
第一行两个整数N,M,N为城市数,M为道路数
接下来M行,每行三个整数,表示两个城市之间的无向边,以及之间的路的长度
输出格式
一行,按递增次序输出若干的数,表示重要的城市。
输入输出样例
输入样例
#1
4 4
1 2 1
2 3 1
4 1 2
4 3 2
输出样例
#1
2
说明
30%的数据: N ≤ 20 N\le 20 N20
60%的数据: N ≤ 100 N\le 100 N100
100%的数据: N ≤ 200 , M ≤ N × ( N − 1 ) 2 , 0 &lt; c ≤ 10000 N\le 200,M\le \frac{N\times (N-1)}{2},0&lt;c\le 10000 N200,M2N×(N1),0<c10000。c即路的长度。
保证不出现重边和自环,如果没有点的话需要输出一行 “No important cities.” 去掉引号 。


思路

因为这道题是在社交网络之后写的吧,所以我就想到了 F l o y d Floyd Floyd的最短路计数。

  • 思路1:如果存在一个点 k k k经过了一个点对 ( i , j ) (i,j) i,j的所有的最短路,也就是说 k k k i i i到达 j j j的最短路的必经之点,这样子的话也就是说k是重要的城市;
  • 思路2:我们可以用一个数组在 F l o y d Floyd Floyd更新的时候保留点对 ( i , j ) (i,j) i,j之间的最短路必经的点 k k k,之后再把保留的 k k k进行顺序上的处理,这样子的话,也是可以得到答案的。

对于上面的两个思路,可能有人会提出要 h a c k hack hack掉第二个方案,我们可以思考一下第二个方案的正确性。

如果对于 F l o y d Floyd Floyd的性质不了解的可以到社交网络这里看一波。

我们可以举一个例子: s − &gt; i − &gt; j − &gt; t s-&gt;i-&gt;j-&gt;t s>i>j>t在这一条路径上,可能有些人(没错,我就是有些人 )会提出说,第二种方案可能在点对 ( s , t ) (s,t) st会漏掉一个点。

但是在考虑一下 F l o y d Floyd Floyd的性质,由于 F l o y d Floyd Floyd始终关心的是三个点之间的关系,就算是在点对 ( s , t ) (s,t) st中仅仅只记录了 j j j这个点,但是在点对 ( s , j ) (s,j) sj中也会记录i这个点,所以并没有遗漏点数的情况。


code1

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

const int nn=203;
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;
	}
	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];
				}
			}
			
	bool im=false;
	for(int k=1;k<=n;++k)/*判断每一个点*/
	{
		bool flag=false;
		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[i][k]*cnt[k][j])
				{
					flag=true;
					break;
				}
			if(flag)	break;
		}
		if(flag)	im=true, printf("%d ",k);
	}
	if(!im)	cout<<"No important cities."<<endl;
	return 0; 
}

code2

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

const int nn=203;

int n,m;
int a[nn][nn],cnt[nn][nn];
bool v[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,-1,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);
	} 
	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]=-1;/*当前这一点对可以通过其他的方式到达*/
				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]=k;
				}
			}
	for(int i=1;i<=n;++i)
		for(int j=i+1;j<=n;++j)
			if(cnt[i][j]!=-1)
				v[cnt[i][j]]=true;
	bool im=false;
	for(int i=1;i<=n;++i)
		if(v[i])
			im=true, printf("%d ",i);
	if(!im)	cout<<"No important cities."<<endl;
	return 0;
} 

最后的小总结

  1. 做Floyd的题的时候需要仔细考虑Floyd的性质是什么?
  2. 做Floyd的题的时候需要注意一下数组的双向性,不能只更新数组矩阵的一半,另一半为空;
  3. 做Floyd的题的时候一般数据范围都比较明显;
  4. 细心,细心,细心!!

不乱于心,不困于情,不畏将来,不念过往,如此,安好。

完……

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据引用[1],dp[u][j]表示在u子树中选取恰好j个人时能获得的最大价值。而根据引用,该问题的时间复杂度为O(log2​104×nm)。 对于洛谷P2143 [JSOI2010] 巨额奖金问题,我们可以使用动态规划来解决。具体步骤如下: 1. 首先,我们需要构建一棵树来表示员工之间的关系。树的根节点表示公司的总经理,其他节点表示员工。每个节点都有一个权值,表示该员工的奖金金额。 2. 接下来,我们可以使用动态规划来计算每个节点的dp值。对于每个节点u,我们可以考虑两种情况: - 如果选择节点u,则dp[u][j] = dp[v][j-1] + value[u],其中v是u的子节点,value[u]表示节点u的奖金金额。 - 如果不选择节点u,则dp[u][j] = max(dp[v][j]),其中v是u的子节点。 3. 最后,我们可以通过遍历树的所有节点,计算出dp[u][j]的最大值,即为所求的巨额奖金。 下面是一个示例代码,演示了如何使用动态规划来解决洛谷P2143 [JSOI2010] 巨额奖金问题: ```python # 构建树的数据结构 class Node: def __init__(self, value): self.value = value self.children = [] # 动态规划求解最大奖金 def max_bonus(root, j): dp = [[0] * (j+1) for _ in range(len(root)+1)] def dfs(node): if not node: return for child in node.children: dfs(child) for k in range(j, 0, -1): dp[node.value][k] = max(dp[node.value][k], dp[node.value][k-1] + node.value) for child in node.children: for k in range(j, 0, -1): for l in range(k-1, -1, -1): dp[node.value][k] = max(dp[node.value][k], dp[node.value][k-l-1] + dp[child.value][l]) dfs(root) return dp[root.value][j] # 构建树 root = Node(1) root.children.append(Node(2)) root.children.append(Node(3)) root.children[0].children.append(Node(4)) root.children[0].children.append(Node(5)) root.children[1].children.append(Node(6)) # 求解最大奖金 j = 3 max_bonus_value = max_bonus(root, j) print("最大奖金为:", max_bonus_value) ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值