算法提高-图论-floyd算法及其扩展应用

floyd算法及其扩展应用

  • floyd算法及其扩展应用
    • AcWing 1125. 牛的旅行
    • AcWing 343. 排序
    • AcWing 344. 观光之旅
    • AcWing 345. 牛站

floyd算法及其扩展应用

AcWing 1125. 牛的旅行

#include 
#include 
#include 
#include 

using namespace std;

typedef pair<double, double> PDD;
#define x first
#define y second

const int N = 155;
double INF = 1e20;
double d[N][N];
double maxd[N];
char g[N][N];
int n;
PDD q[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;
            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)//说明ij两个牧区在同一个牧场
                maxd[i] = max(maxd[i], d[i][j]);
        
        r1 = max(r1, maxd[i]);
    }
    
    double r2 = INF;
    for (int i = 0; i < n; i ++ )
    {
        for (int j = 0; j < n; j ++ )
        {//和INF / 2比较的原因是为了避免double精度不够,这样更保险一些
            if (d[i][j] > INF / 2)//说明ij两个牧区不在同一个牧场,需要连一条线、
                //r2 = min(r2, maxd[i] + d[i][j] + maxd[j]); 不能用d[i][j]获得i和j之间的距离,因为二者本来就不连通
                 r2 = min(r2, maxd[i] + maxd[j] + get_dist(q[i], q[j]));
        }
    }
    
    printf("%.6lf\n", max(r1, r2)); 
    return 0;
}

AcWing 343. 排序

#include 
#include 
#include 

using namespace std;

const int N = 26;

int g[N][N], d[N][N];
bool st[N];

int n, m;

void floyd()
{
    memcpy(d, g, sizeof d);
    
    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];
}

int check()
{
    for (int i = 0; i < n; i ++ )//自我冲突
        if (d[i][i] == 1) return 2;
    
    for (int i = 0; i < n; i ++ )
        for (int j = i + 1; j < n; j ++ )//枚举组合数,看两两是否有关系
            if(d[i][j] == 0 && d[j][i] == 0) 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(g, 0, sizeof g);
        int type = 0, t;
        for (int i = 1; i <= m; i ++ )
        {
            char str[5];
            cin >> str;
            
            int a = str[0] - 'A', b = str[2] - 'A';
            
            if (type == 0)//只要type !=0 就不会更新迭代关系 后面输入的关系可能导致type从2或者1变成0了但我们记录第一次
            {
                g[a][b] = 1;
                floyd();//预处理d数组
                type = check();
                if(type != 0)  t = i;//每输入一组大小关系,就说说明迭代了一次,
            }
            
        }
        if (type == 2) printf("Inconsistency found after %d relations.\n", t);
        else if (type == 1)
        {
            printf("Sorted sequence determined after %d relations: ", t);
            memset(st, 0, sizeof st);//多组数据,因此每次用到st之前都要重置一下
            for (int i = 0; i < n; i ++ ) cout << get_min();
            printf(".\n");
        }
        else printf("Sorted sequence cannot be determined.\n");
    }
    return 0;
}

AcWing 344. 观光之旅

#include 
#include 
#include 

const int N = 110, INF = 0x3f3f3f3f;
using namespace std;

int path[N], cnt;
int pos[N][N];
int g[N][N];//稠密图所以用邻接矩阵
int d[N][N];

int n, m;

void get_path(int i, int j)
{
    if (pos[i][j] == 0) return;
    int k = pos[i][j];
    get_path(i, k);
    path[cnt ++ ] = 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;//这个真不记得了,用邻接举证存的时候需要初始化
    
    for (int i = 0; i < m; i ++ )
    {
        int a, b, c;
        cin >> a >> b >> c;
        g[a][b] = g[b][a] = min(g[a][b], c);
    }
    memcpy(d, g, sizeof d);
    
    int res = INF;
    for (int k = 1; k <= n; k ++ )//我不知道为什么path记录要放在floyd前面
    {
        //path记录
        //这里不超过k其一原因是因为定义强制k为环上最大节点,
        //其次,当前最大循环第k次的时候,默认环上的结点已经有k了,
        //而且这里要保证三个点需要互异,因此i和j不同,且都要不等于k。
        //不然就会有两个点之间形成的环了。
        for (int i = 1; i < k; i ++ )
            for (int j = i + 1; j < k; j ++ )
            {
                if (res > (long long)d[i][j] + g[j][k] + g[k][i])
                {
                    res = d[i][j] + g[j][k] + g[k][i];
                    cnt = 0;//每次更新path都要重置cnt
                    path[cnt ++ ] = k;
                    path[cnt ++ ] = i;
                    get_path(i, j);
                    path[cnt ++ ] = j;
                }
            }
        //floyd
        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;//更新的同时记录通过哪个点转移的状态
                }
                
    }
    
    if (res == INF) cout << "No solution.";
    else 
    {
        for(int i = 0; i < cnt; i ++ ) cout << path[i] << " ";
    }
        
    return 0;
}

AcWing 345. 牛站

离散化 (只要用到200个点,但是题目给的点编号是1-1000)+ 倍增(快速幂)+ flyod变式(将递推公式改变了)
能用快速幂的原因是递推公式里面的两端路径两两之间相互独立,用结合律就可以用快速幂。矩阵乘法能用快速幂的原因也是矩阵乘法中两两矩阵之间具有结合律
帮助理解的博客1
帮助理解的博客2

#include 
#include 
#include 

using namespace std;

const int N = 1010;//这里N是点数,不是题目给的边数

int k, m, S, E;
int n;//离散化后点的数量

int g[N][N];
int res[N][N];//相当于之前的d数组

                                              //好像是二维数组传参必须这样
void floyd(int c[][N], int a[][N], int b[][N])//我不知道为什么这里第二维必须要明确开N,否则编译报错
{
    static int temp[N][N];temp数组作为a数组和b组数相乘的结果,缓冲结果
    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], a[i][k] + b[k][j]);
    memcpy(c, temp, sizeof temp);//不能sizeof c,因为c只是一个指针在这里 
                                                    
   // cout << sizeof c << " " << sizeof temp;// sizeof c = 二维数组的指针 8
                                           //sizeof temp = 二维数组的大小40804008
}

void qmi()
{
    memset(res, 0x3f, sizeof res);
    for(int i=1;i<=n;i++) res[i][i]=0;//除了i->i是经过1条边位0,i到其它点都是经过0条边距离为0x3f,这样我们保持基数是0这样结合律的时候才正确
    while(k)
    {
        if (k & 1) floyd(res, res, g);//结合律 res = res * g, 
                                      //如果这一位是1的话,res就结合上这个结果
                                      
        floyd(g, g, g);//倍增g = g * g
        k >>= 1;
    }
}

int main ()
{
    cin >> k >> m >> S >> E;
    map<int, int> id;//int - int去离散化题目给的下标
    
    id[S] = ++ n, id[E] = ++ n;//题目说了数据保证一定有解,所以就没写代码的健壮性判断了,id.count(S);
    S = id[S], E = id[E];
    
    memset(g, 0x3f, sizeof g);//这里不需要将g[i][i]初始化为0,因为题目说了至少经过1条边(2≤N≤106)
                              //如果没有i->i的边的话,i->i就是正无穷,只能通过自环从i到i
    for (int i = 0; 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);
    }
    
    qmi();
    
    cout << res[S][E];//之前已经离散化过了S和E
    return 0;
}

你可能感兴趣的:(算法,图论,c++,蓝桥杯,floyd)