7.1.floyd算法解决传递闭包问题
问题:在游戏中,两人之间会存在胜负关系,寻找不能确定胜负关系的二元组合。
前提:(a,b) (b,a)属于同一个二元组合。
问题分析:一个人可以视为一个点,胜负关系可理解为两个点之间存在边。
则问题转化为在图中寻找中转可连通的传递闭包。
实现:用矩阵对点关系进行存储,两点有胜负关系则记为1。
注意:对于一条边而言,我们在矩阵中只更新一个值为1。
floyd算法说明:在图中不断加入可中转点,加入中转点后对图的连通到达情况进行更新。
算法空间复杂度为:n^2 算法的时间复杂度为:n^3
本题中的剪枝说明:
在本题中,对于一个点的自身而言,(a,a)始终为:0。
因而在中转点等于 行数、列数; 或行数 = 列数 时,都应进行剪枝。
if(!map[j][k]) continue; 当第一个中转点不可达时,中转无意义,进行剪枝。
最终结果:图矩阵中非0个数,代表确定胜负关系的二元组。
二元组的个数为:n*(n-1)
故不确定的二元组为:总二元组数目-确定胜负关系的二元组
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn = 500+5;
int n,m,map[maxn][maxn];
void floyd()
{
for(int k=1;k<=n;k++)
for(int j=1;j<=n;j++)
{
if(j==k) continue;
if(!map[j][k]) continue;
for (int p = 1; p <= n; p++)
{
if(j==p||p==k) continue;
map[j][p]=map[j][p]|(map[j][k]&map[k][p]);
}
}
}
int main()
{
int u,v;
int num;
scanf("%d", &num);
for (int i = 0; i < num; i++)
{
scanf("%d%d", &n, &m);
memset(map,0,sizeof(map));
for(int i = 1; i <= m; i++)
{
scanf("%d%d",&u,&v);
map[u][v] = 1;
}
floyd();
int count=0;
for(int i=1; i<=n;i++)
for(int j=1; j<=n;j++)
{
if(map[i][j]!=0)
count++;
}
int ans=n*(n-1)/2;
ans=ans-count;
printf("%d\n",ans);
}
return 0;
}
7.2.dijuskal算法寻找最短路径问题
题目描述:
众所周知,TT 有一只魔法猫。
今天他在 B 站上开启了一次旅行直播,记录他与魔法猫在喵星旅游时的奇遇。
TT 从家里出发,准备乘坐猫猫快线前往喵星机场。
猫猫快线分为经济线和商业线两种,它们的速度与价钱都不同。
当然啦,商业线要比经济线贵,TT 平常只能坐经济线,
但是今天 TT 的魔法猫变出了一张商业线车票,可以坐一站商业线。
假设 TT 换乘的时间忽略不计,请你帮 TT 找到一条去喵星机场最快的线路,
不然就要误机了!
input:
输入包含多组数据。每组数据第一行为 3 个整数 N, S 和 E
(2 ≤ N ≤ 500, 1 ≤ S, E ≤ 100),即猫猫快线中的车站总数,
起点和终点(即喵星机场所在站)编号。
下一行包含一个整数 M (1 ≤ M ≤ 1000),即经济线的路段条数。
接下来有 M 行,每行 3 个整数 X, Y, Z (1 ≤ X, Y ≤ N, 1 ≤ Z ≤ 100),
表示 TT 可以乘坐经济线在车站 X 和车站 Y 之间往返,其中单程需要 Z 分钟。
下一行为商业线的路段条数 K (1 ≤ K ≤ 1000)。
接下来 K 行是商业线路段的描述,格式同经济线。
所有路段都是双向的,但有可能必须使用商业车票才能到达机场。保证最优解唯一。
output:
对于每组数据,输出3行。第一行按访问顺序给出 TT 经过的各个车站(包括起点和终点)
第二行是 TT 换乘商业线的车站编号
(如果没有使用商业线车票,输出"Ticket Not Used",不含引号)
第三行是 TT 前往喵星机场花费的总时间。
本题不忽略多余的空格和制表符,且每一组答案间要输出一个换行
dijuskal算法的思想是:
选择权重最小的边,然后对所有的边权进行更新。
这样,在一个图中,我们进行(n-1)次选择。
也就是说到每个点的路径都是当前可选的最短路径。
因而,通过该算法选出了最小连通路径。
实现:本题采用链式前向星结构对点与边进行处理,
这样可将当前起点所连的所有边进行对应存储,便于之后遍历。
首先对经济线利用dijuskal算法:
找出最小连通路径,并在确定最小连通路径的过程中,
用dis[]、pre[]数组对当前起点到对应点的距离进行标记,
对到达点的前一个点进行标记。
通过对起点 s,终点 e,利用两次dijuskal算法。将数据进行统计。
对于一次商务线的选择:
当不选择商务线时,最短路径值就是dis1[e],从s出发到e的距离。
是否选择商务线,在于加入该条商务线,能否缩短当前的最小路径。
遍历所有商务线,选取使得路径最短的那一条。
关于路径的输出:
1.不选择商务线进行路径输出
pre1[]数组中存放当前点的前一个节点。
首先拿出节点e 再不断找出上一个节点,直到找到节点s 停止。
这样将路径统计到数组a[]中,进行处理输出即可。
2.选择商务线进行路径输出
选择了商务线,那么该商务线的两端记为 ans_u,ans_v;
s—>ans_u 使用s起始的连通路径;
ans_v—>e 使用e起始的连通路径;
因而通过 pre1[]从ans_u起始,向前遍历找到s,并对寻找的过程点进行标记。
对pre2[]从e开始,向前遍历找到e,并对寻找过程进行标记。
#include<iostream>
#include<cstring>
using namespace std;
const int maxx=1000000;
int n,m,k,s,e,num;
int ans=maxx,head[maxx],dis1[maxx],dis2[maxx];
int pre1[maxx],pre2[maxx],sum,a[maxx];
bool flag[maxx];
int ans_u,ans_v,ans_w;
struct node
{
int to;
int w;
int next;
}e1[maxx];
void add_edge(int from,int to,int w)
{
e1[++num].to=to;
e1[num].w=w;
e1[num].next=head[from];
head[from