加载中...

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;
}

posted @ 2022-05-06 16:32  liang302  阅读(46)  评论(0编辑  收藏  举报