本章一共四道题目。
1.最短路径问题
2.Cow Contest
3.猪仙修桥
4.虫洞(wormhole)
1.最短路径问题
Description
平面上有 n 个点(n ≤ 100),每个点的坐标均在 -10000 至 10000 之间。其中的一些点之间有连线。若有连线,则表示可从一个点到达另一个点,即两点间有通路,通路的距离为两点直线的距离。现在的任务是找出从一点到另一点之间的最短路径。
Format
Input
共有 n + m + 3 行,其中: 第一行为一个整数 n。 第 2 行到第 n + 1 行(共 n 行),每行的两个整数 x 和 y,描述一个点的坐标(以一个空格隔开)。
第 n + 2 行为一个整数 m,表示图中的连线个数。
此后的 m 行,每行描述一条连线,由两个整数 i,j 组成,表示第 i 个点和第 j 个点之间有连线。
最后一行,两个整数 s 和 t,分别表示源点和目标点。
Output
一个实数(保留两位小数),表示从 S 到 T 的最短路径的长度。
Samples
输入数据 1
5
0 0
2 0
2 2
0 2
3 1
5
1 2
1 3
1 4
2 5
3 5
1 5
输出数据 1
3.41
思路
这题很简单,要注意的是题中的距离指的是欧几里得距离,设两个点的坐标分别为(x1,y1),(x2,y2),那么两点的距离为。
代码
#include<bits/stdc++.h>
using namespace std;
struct stu{
int x,y;
}a[100010];
int n,m,b[100][100],l,r,ss,t;
double dp[100][100];
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i].x>>a[i].y;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
dp[i][j]=10000000000.0;//初始化赋极大值
}
}
cin>>m;
for(int i=1;i<=m;i++){
cin>>l>>r;
b[l][r]=1;
dp[l][r]=sqrt(abs(a[l].x-a[r].x)*abs(a[l].x-a[r].x)+abs(a[l].y-a[r].y)*abs(a[l].y-a[r].y));//计算距离
dp[r][l]=sqrt(abs(a[l].x-a[r].x)*abs(a[l].x-a[r].x)+abs(a[l].y-a[r].y)*abs(a[l].y-a[r].y));//双向边
}
cin>>ss>>t;//两个点
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]);//floyd
}
}
}
cout<<fixed<<setprecision(2)<<dp[ss][t];
return 0;
}
2.Cow Contest
Description
Farmer John的 N(1 ≤ N ≤ 100)头奶牛们最近参加了场程序设计竞赛,每头奶牛的编程能力不尽相同,并且没有哪两头奶牛的水平相同,也就是说,奶牛们的编程能力有明确的排名。
整个比赛被分为了若干轮,每一轮是两头指定编号的奶牛的对决,如果编号为 A 的奶牛的编程能力强于编号为 B 的奶牛(1 ≤ A,B ≤ N,A ≠ B),那么他们的对决中,编号为 A 的奶牛总是能胜出。
Farmer John想知道奶牛们编程能力的具体排名,于是他找来了奶牛们所有 M(1 ≤ M ≤ 4500)轮比赛的结果,希望你能根据这些信息,推断出尽可能多的奶牛的编程能力排名。比赛结果保证不会自相矛盾。
Format
Input
第一行,两个整数 N 和 M。
第 2 至第 M + 1 行,每行包含两个空格分隔的整数,用于描述每一轮竞争对手和单轮比赛的结果。(A B 代表这一轮 A 赢了 B)
Output
一行,表示可以确定排名的奶牛数量。
Samples
输入数据 1
5 5
4 3
4 2
3 2
1 2
2 5
输出数据 1
2
Explanation
样例一说明:
编号为 2 的奶牛输给了编号为 1、3、4 的奶牛,也就是说他的水平比这 3 头奶牛都差。而编号为 5 的奶牛又输在了她的手下,也就是说她的水平比编号为 5 的奶牛强一些。于是,编号为 2 的奶牛排名必然是第 4,编号为 5 的奶牛水平必然最差,其他三头奶牛的排名仍无法确定。
思路
这道题也不难,因为只要确定了(i,k)和(k,j)的关系(1<=k<=n),那么就可以确定(i,j)了。要注意的是不能建双向边,因为a>b,b<c不能确定b的排名。
代码
#include<bits/stdc++.h>
using namespace std;
struct stu{
int x,y;
}a[100010];
int n,m,b[110][110],l,r,k,ss,t,maxx,f;
int dp[101][101];
int main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
cin>>l>>r;
dp[l][r]=1;//不能建双向边,因为仅能确定l赢了r
}
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(i!=j&&i!=k&&j!=k)dp[i][j]=dp[i][k]&dp[k][j]|dp[i][j];//简写版floyd,这又在这种情况下才可以用
}
}
}
for(int i=1;i<=n;i++){
k=1;
for(int j=1;j<=n;j++){
if(i==j)continue;
else{
k=k&(dp[i][j]|dp[j][i]);//简写(一定要与每只奶牛都能确定关系)
}
}
maxx+=k;
}
cout<<maxx;
return 0;
}
3.猪仙修桥
Description
猪仙多次修桥,已经小有名气了,于是,不断有人来找猪仙修桥。一天,猪仙接到了这样的任务。在一片大海上有 N 座岛屿,需要使它们联通。但是由于经费问题,岛上的居民想在任意两点间距离最短的前提下,用尽可能少的投资把各个岛屿连接起来。需要注意的是,并不是任两个岛屿间都能直接连接,且花费和距离成正比。
但是大家知道,猪仙实际上是不会修桥的,所以他需要大家的帮助。
Format
Input
第一行是一个数 N(N < 100)表示有 N 个岛屿。
以下 N 行,每行有 N 个数,第 i 行第 j 个数表示岛屿 i 和岛屿 j 间建路的花费 Wij(0 < Wij < 10000,Wij = Wji),非正数表示两地不可直连。输入数据保证有解。
Output
第一行输出总花费。
然后输出 N 行,每行 N 个数,第 i 行第 j 个数表示岛屿 i 到岛屿 j 的路。用 1 表示这选取条路,0 则不选。
Samples
输入数据 1
3
0 2 1
2 0 3
1 3 0
输出数据 1
3
0 1 1
1 0 0
1 0 0
思路
因为“各个岛屿连接起来”,所以这道题可以用最小生成树做,有兴趣的大佬可以去试一下。假设刚开始每条路都修,那么floyd时只要发现(i,j)可以由(i,k)和(k,j)替代,那么无需使用(i,j),输出时输出0就可以了。要注意的是非正数表示不可达。
代码
#include<bits/stdc++.h>
using namespace std;
int n,f,sum;
int dp[101][101],v[101][101];
int main(){
cin>>n;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cin>>dp[i][j];
if(i!=j)v[i][j]=1;//刚开始都可以用
if(dp[i][j]<0)dp[i][j]=1e9;//不可达
}
}
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(i!=j&&i!=k&&j!=k&&dp[i][j]>=dp[i][k]+dp[k][j]){
v[i][j]=0;//不选这条,因为有更优解
dp[i][j]=dp[i][k]+dp[k][j];//更新
}
}
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(v[i][j])sum+=dp[i][j];//加上费用
}
}
cout<<sum/2<<endl;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cout<<v[i][j]<<" ";///输出
}
cout<<endl;
}
return 0;
}
4.虫洞(wormhole)
Description
John在他的农场中闲逛时发现了许多虫洞。虫洞可以看作一条十分奇特的有向边,并可以使你返回到过去的一个时刻(相对你进入虫洞之前)。John的每个农场有 M 条小路(无向边)连接着 N (从1...N 标号)块地,并有 W 个虫洞(有向边)。其中 1<=N<=500,1<=M<=2500,1<=W<=200 。 现在John想借助这些虫洞来回到过去(出发时刻之前),请你告诉他能办到吗。 John将向你提供 F(1<=F<=5) 个农场的地图。没有小路会耗费你超过 10000 秒的时间,当然也没有虫洞回帮你回到超过 10000 秒以前。
Format
Input
多组数据,第一行一个整数 F, 表示农场个数。
每组数据第一行三个整数 N,M,W。
接下来 M 行,每行三个数 S,E,T 。表示在标号为 S 的地与标号为 E 的地中间有一条用时 T 秒的小路。
接下来 W 行,每行三个数 S,E,T 。表示在标号为 S 的地与标号为 E 的地中间有一条可以使John到达 T 秒前的虫洞。
Output
对于每组数据,如果John能在这个农场实现他的目标,输出 YES
,否则输出 NO
。
Samples
输入数据 1
2
3 3 1
1 2 2
1 3 4
2 3 1
3 1 3
3 2 1
1 2 3
2 3 4
3 1 8
输出数据 1
NO
YES
思路
这题有多组测试数据,很坑。注意每次都要初始化为极大值,但(i,i)要初始化为0(不用走),如果floyd后(i,i)反而小于0,那么说明存在一组路径可以穿越到以前。只要有一个点符合上述情况即可。
代码
#include <bits/stdc++.h>
using namespace std;
int n,m,w,s,e,t,ff,f[505][505];
bool flag;
signed main(){
cin>>ff;
for(int i=1;i<=ff;i++){
cin>>n>>m>>w;
for(int i=1;i<=500;i++)for(int j=1;j<=500;j++)f[i][j]=1e9;//初始化(多测要清空!)
for(int i=1;i<=m;i++){
cin>>s>>e>>t;
f[s][e]=min(f[s][e],t);//有重边
f[e][s]=min(f[e][s],t);//双向边
}
for(int i=1;i<=w;i++){
cin>>s>>e>>t;
f[s][e]=min(f[s][e],-t);//t秒前
}
flag=0;
for(int i=1;i<=n;i++){
f[i][i]=0;//自己本身不用走,初始化为0,如果floyd后比零小,那么说明可以穿越
}
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
f[i][j]=min(f[i][k]+f[k][j],f[i][j]);
}
}
}
for(int i=1;i<=n;i++){
if(f[i][i]<0)flag=1;//可以穿越
}
if(flag)cout<<"YES\n";
else cout<<"NO\n";
}
return 0;
}