floyd 2.求传递闭包(top()也能做) 3.最小环 4.恰好经过k条边
牛的旅行https://www.acwing.com/problem/content/1127/
求两个连通块连接后最大直径 (可能是连接之前的直径,可能是连接之后的最大值)
1.如果能到达 连接两个点的距离 建图
2.建图后floyd
3.取dmin
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>
#define x first
#define y second
using namespace std;
typedef pair<double, double> PDD;
const int N = 155;
const double INF = 1e20;
int n;
PDD q[N];
double d[N][N];
double maxd[N];
char g[N][N];
double get_dist(PDD a, PDD b)
{
double dx = a.x - b.x;
double dy = a.y - b.y;
return sqrt(dx * dx + dy * dy);
}
int main()
{
cin >> n;
for (int i = 0; i < n; i ++ ) cin >> q[i].x >> q[i].y;//输入点对的横纵坐标
for (int i = 0; i < n; i ++ ) cin >> g[i];//存领接矩阵
for (int i = 0; i < n; i ++ )
for (int j = 0; j < n; j ++ )
if (i == j) d[i][j] = 0;//到自己距离为0
else if (g[i][j] == '1') d[i][j] = get_dist(q[i], q[j]);//发现如果能“直接”走到的话,计算点的距离
else d[i][j] = INF;//如果不能走到 距离设置为正无穷
for (int k = 0; k < n; k ++ )
for (int i = 0; i < n; i ++ )
for (int j = 0; j < n; j ++ )
d[i][j] = min(d[i][j], d[i][k] + d[k][j]);//做一遍的佛洛依德 计算“间接”两点的最短路
double r1 = 0;
for (int i = 0; i < n; i ++ )
{
for (int j = 0; j < n; j ++ )
if (d[i][j] < INF / 2)//说明i走到的j
maxd[i] = max(maxd[i], d[i][j]);//如果走不到 求maxd i存i能走到的点的最大值
r1 = max(r1, maxd[i]);//r1存某个连通块的最大值
}
double r2 = INF;
for (int i = 0; i < n; i ++ )
for (int j = 0; j < n; j ++ )
if (d[i][j] > INF / 2)//如果不能走到
r2 = min(r2, maxd[i] + maxd[j] + get_dist(q[i], q[j]));//计算不能走到的最小值 也就是两个连通块的最小值
printf("%.6lf\n", max(r1, r2));
return 0;
}
排序https://www.acwing.com/problem/content/345/
求传递背包 给出若干按个关系问是否根据这些关系给出对应的顺序
可以top()排序
排序后,排序数组不为n个,则表示有环,矛盾,跳出循环
排序后,排序数组为n个,但是在过程中,有2个或以上的点在队列中,表示拓扑序并不唯一,那么此时并不能确定所有点的顺序,因此进行下一次循环
排序后,排序数组为n个,且在过程中,队列中一直只有一个,拓扑序唯一,输出结果,跳出循环
floyd
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 26;
int n, m;
bool d[N][N];
bool st[N];
void floyd() //通过floyd来逐渐更新每两个点的连通情况
{
for(int k = 0; k < n; k ++)
for(int i = 0; i < n; i ++)
for(int j = 0; j < n; j ++)
d[i][j] |= d[i][k] & d[k][j];
//if(!d[i][j]) d[i][j] = d[i][k] & d[k][j];是错误的
//如果原来是 i->k k->j 通过这样让i->j 同时如果本来i->j也一定要i->j
}
int check()
{
for(int i = 0; i < n; i ++) //矛盾情况 发现自己能到达自己
if(d[i][i]) return 2;
for(int i = 0; i < n; i ++) //不能确定情况
for(int j = 0; j < i; j ++)
if(!d[i][j] && !d[j][i]) return 0;//发现不能互相到达
return 1;
}
char get_min() //每次取出最小的值 用于输出大小关系
{
for(int i = 0; i < n; i ++)
if(!st[i]) //如果当前这个数没有取出
{
bool flag = true;
for(int j = 0; j < n; j ++)//判断是否最小
if(!st[j] && d[j][i])//如果有点比他更小的 就要推出
{
flag = false;
break;
}
if(flag)
{
st[i] = true;//没有更小的 那就是这个了
return 'A' + i;
}
}
}
int main()
{
while(cin >> n >> m, n || m)
{
memset(d, 0, sizeof d);
int type = 0, t; // t 记录轮次 type记录判断出来与否的标志
for(int i = 1; i <= m; i ++)
{
char str[5];
cin >> str;
int a = str[0] - 'A', b = str[2] - 'A';
//获得两个关系
if(!type)//如果还没确定下来
{
g[a][b] = 1;//a小于b 连接一条边
floyd();//每次连接做一次floyd
type = check();//检查下加入这次后的情况
if(type) t = i;//t找到是哪个关系
}
}
if(!type) puts("Sorted sequence cannot be determined.");
else if(type == 2) printf("Inconsistency found after %d relations.\n", t);
else
{
memset(st, 0, sizeof st);
printf("Sorted sequence determined after %d relations: ", t);
for(int i = 0; i < n; i ++) printf("%c", get_min());
printf(".\n");
}
}
return 0;
}
观光之旅
求最小环
和以前的题不同 这里是去掉某个环的某点 来判断的
因为对于 i-j-k-i 这个环 如果想要去掉 去掉k这个点 剩下的d[i][j]一定是最短的(满足dp) 所以d[i][j]+g[k][j]+g[j][i]
1.本题的思路就是考虑最小环里面节点编号最大的节点为k,且环里面与k相连的两个点为i,j,环的长度为g[i][k]+g[k][j]+d[j][i];
2.则d[j][i]则表示j到i且经过的节点编号小于k,因为在环中k就是最大的,只能经过小于k的节点了;
3.则这与floyd中k次循环开始前的d[i][j]意义相同;
4.那就不妨在floyd的第一重循环就求一下以k为最大节点编号的环的长度,美滋滋😁,注意这里的k必须与节点的意义一样:0-n-1或1-n;
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 110, INF = 0x3f3f3f3f;
int n, m;
int d[N][N], g[N][N]; // d[i][j] 是不经过点
int pos[N][N]; // pos存的是中间点k
int path[N], cnt; // path 当前最小环的方案, cnt环里面的点的数量
// 递归处理环上节点
void get_path(int i, int j) {
if (pos[i][j] == 0) return; // i到j的最短路没有经过其他节点
int k = pos[i][j]; // 否则,i ~ k ~ j的话,递归处理 i ~ k的部分和k ~ j的部分
get_path(i, k);
path[cnt ++] = k; // k点放进去
get_path(k, j);
}
int main() {
cin >> n >> m;
memset(g, 0x3f, sizeof g);
for (int i = 1; i <= n; i ++) g[i][i] = 0;
while (m --) {
int a, b, c;
cin >> a >> b >> c;
g[a][b] = g[b][a] = min(g[a][b], c);
}
int res = INF;
memcpy(d, g, sizeof g);
// dp思路, 假设k是环上的最大点, i ~ k ~ j(Floyd的思想)
for (int k = 1; k <= n; k ++) {
// 求最小环,
//至少包含三个点的环所经过的点的最大编号是k
for (int i = 1; i < k; i ++) // 至少包含三个点,i,j,k不重合
for (int j = i + 1; j < k; j ++)
// 由于是无向图,
// ij调换其实是跟翻转图一样的道理
// 直接剪枝, j从i + 1开始就好了
// 更新最小环, 记录一下路径
if ((long long)d[i][j] + g[j][k] + g[k][i] < res) {
// 注意,每当迭代到这的时候,
// d[i][j]存的是上一轮迭代Floyd得出的结果
// d[i][j] : i ~ j 中间经过不超过k - 1的最短距离(k是不在路径上的)
res = d[i][j] + g[j][k] + g[k][i];
cnt = 0;
path[cnt ++] = k; // 先把k放进去
path[cnt ++] = i; // 从k走到i(k固定的)
get_path(i ,j); // 递归求i到j的路径
path[cnt ++] = j; // j到k, k固定
}
// Floyd, 更新一下每个i到每个j经过k这个点的最短路径,来保证最小的d[i][j];
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= n; j ++)
if (d[i][j] > d[i][k] + d[k][j]) {
d[i][j] = d[i][k] + d[k][j];
pos[i][j] = k;
//用pos(i,j)=k表示在floyd计算过程中dis(i,j)当前最小值的路径包含顶点k(由其更新而来).表示i和j为顶点的k
}
}
if (res == INF) puts("No solution.");
else {
for (int i = 0; i < cnt; i ++) cout << path[i] << ' ';
cout << endl;
}
return 0;
}
从起点到终点恰好经过k条边的最短路径
类Floyd算法+qmi(快速幂)
https://www.acwing.com/problem/content/347/
d[i,j,k]从i到j只经过1~k的
d[k,i,j]
1.首先这里n=1000 floyd是o3的算法 所以到时候会超时 用的点没有1000个点 最多只有2000个点 ->离散化 获得新点
2.恰好可以求得的
floyd ijk 表示从i到j 只经过i~k的话最短路径是多少(不可以处理负环)
https://cdn.acwing.com/media/article/image/2020/07/28/15856_24fba108d0-批注-2020-07-28-120823.png
把k的含义调换了
类floyd kij 表示从i到j,恰好经过k条边的最短路径 所以和floyd的意思是不同的 求 a+b,i,j表示从i到j恰好经过k调边的最短路径=min(a,i,k)+min(b,k,j) k是中间点
https://cdn.acwing.com/media/article/image/2020/07/28/15856_048f9670d0-批注-2020-07-28-122253.png
求法:d(2,j,k)->d(4,j,k)->(8,j,k)
https://cdn.acwing.com/media/article/image/2021/03/27/42785_80d05e928f-QQ浏览器截图20210327211948.png
3.优化 矩阵快速幂 利用结合律
使用矩阵相加
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
using namespace std;
const int N=1010;
int K,n,m,S,E;
int g[N][N];//开始为只经过一条边的数组
int res[N][N];//答案数组
void floyd(int a[][N],int b[][N],int c[][N])
{
static int temp[N][N];//temp数组作为b数组和c组数相乘的结果 必须新开数组 不能直接用数组
memset(temp,0x3f,sizeof temp);
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
temp[i][j]=min(temp[i][j],b[i][k]+c[k][j]);
memcpy(a,temp,sizeof temp); //复制得到结果
}
void qmi()//快速幂套floyd
{
while(K)//跑k次
{
if(K&1) floyd(res,res,g);//更新答案数组,即res=res*g
floyd(g,g,g);//将g数组倍增,即g=g*g
K>>=1;
}
}
int main()
{
cin>>K>>m>>S>>E;
map<int,int> id;
memset(g,0x3f,sizeof g);
//因为g数组必须走过一条边,所以g[i][i]不初始化为0,除非i->i之间有边
id[S]=++n,id[E]=++n;
S=id[S],E=id[E];
//离散化过程,因为题目中给的编号并不是从1开始而是随机的;
for(int i=1;i<=m;i++)
{
int a,b,c;
cin>>c>>a>>b;
if(!id.count(a)) id[a]=++n;
if(!id.count(b)) id[b]=++n;
a=id[a],b=id[b];
g[a][b]=g[b][a]=min(g[a][b],c);
//从第一行都这里都是图论里离散化的套路
}
memset(res,0x3f,sizeof res);
for(int i=1;i<=n;i++) res[i][i]=0;//还没开始走 即通过0条边的时候 到自己的距离应该是0
qmi();
printf("%d",res[S][E]);//输出答案 res一直存储结果
return 0;
}