ACwing算法提高课-第三章图论笔记(1)

1.单源最短路的建图方式

(1)热浪(djsl优化)

#include<bits/stdc++.h>
using namespace std;
#define pii pair<int,int>
const int N=2510;
const int M=6210*2;
int n,m,s,t;
int dist[N];//起点s到各点的距离
bool st[N];
int h[N],e[M],ne[M],w[M],idx;
//邻接表
void add(int a,int b,int c)
{
    e[idx]=b;
    w[idx]=c;
    ne[idx]=h[a];
    h[a]=idx++;
}
void djsl()
{
    memset(dist,0x3f,sizeof(dist));
    priority_queue<pii,vector<pii>,greater<>>heap;//小根堆
    dist[s]=0;
    heap.push({dist[s],s});
    //h的第二个存起点到某点,第一个存起点到这个点的最短距离
    while(heap.size())
    {
        pii t=heap.top();
        heap.pop();
        int u=t.second;
        if(st[u]!=0)continue;
        st[u]=1;
        for(int i=h[u];~i;i=ne[i])
        {
            int j=e[i];
            if(dist[j]>dist[u]+w[i])
            {
                dist[j]=dist[u]+w[i];
                heap.push({dist[j],j});
            }
        }
    }
}
int main()
{
    memset(h,-1,sizeof(h));
    cin>>n>>m>>s>>t;
    while(m--)
    {
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);
        add(b,a,c);
    }
    djsl();
    cout<<dist[t]<<endl;
    return 0;
} 

(2)信使(Floy三层循环)

#include<bits/stdc++.h>
using namespace std;
//无向连通图
//求起点1到所有点的距离总和最短
const int N=100+10;
const int M=200+10;
#define inf 0x3f3f3f3f
int n,m;
//稠密图邻接矩阵
//Floyd
int g[N][N];
void Floy()
{ 
    for(int k=1;k<=n;k++)
    {
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=n;j++)
            {
                g[i][j]=min(g[i][j],g[i][k]+g[k][j]);
            }
        }
    }
}
//因为N最大为100,可以使用Floy算法三层循环
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            if(i==j)g[i][j]=0;
            else g[i][j]=inf;
        }
    }
    for(int i=0;i<m;i++)
    {
        int a,b,k;
        cin>>a>>b>>k;
        g[a][b]=g[b][a]=min(g[a][b],k);
    }
    Floy();
    int ans=0;
    for(int i=1;i<=n;i++)
    {
        ans=max(ans,g[1][i]);
    }
    if(ans==0x3f3f3f3f)cout<<-1<<endl;
    else cout<<ans<<endl;
    return 0;
}

(3)香甜的黄油(djsl优化)

#include<bits/stdc++.h>
using namespace std;
//djsl:枚举每个黄油的位置(枚举起点的位置)
//使其到其它每个点的距离之和最短
//注意点:一个牧场可能没有牛,但是在确保能和其它点连通的情况下可以是起点
//堆优化djsl(邻接表)
const int N=2e4+10;
#define pii pair<int,int>
int h[N],e[N],ne[N],w[N],idx;
void add(int a,int b,int c)
{
    e[idx]=b;
    w[idx]=c;
    ne[idx]=h[a];
    h[a]=idx++;
}
int n,p,c;
//n头牛,p个牧场,c路的个数
int dist[N];
//每个节点到起点的最短路
bool st[N];
int cnt[N];
//记录每个牧场有多少奶牛
int ans=0x3f3f3f3f;//答案取min操作注意初始无穷大
void djsl(int x)
{
    memset(st,0,sizeof(st));
    memset(dist,0x3f3f3f3f,sizeof(dist));
    dist[x]=0;
    priority_queue<pii,vector<pii>,greater<>>heap;
    heap.push({dist[x],x});
    while(heap.size())
    {
        auto t=heap.top();
        heap.pop();
        int u=t.second;
        if(st[u])continue;
        st[u]=1;
        for(int i=h[u];~i;i=ne[i])
        {
            int j=e[i];
            if(dist[j]>dist[u]+w[i])
            {
                dist[j]=dist[u]+w[i];
                heap.push({dist[j],j});
            }
        }
    }
    return;
}
int main()
{
    memset(h,-1,sizeof(h));//邻接表记得初始
    cin>>n>>p>>c;
    while(n--)
    {
        //cow为牛所在牧场的编号
        int cow;
        cin>>cow;
        cnt[cow]++;
    }
    while(c--)
    {
        int u,v,k;
        cin>>u>>v>>k;
        add(u,v,k);
        add(v,u,k);
    }
    //枚举每个牧场作为起点
    for(int i=1;i<=p;i++)
    {
        djsl(i);
        int sum=0;
        //判断这个起点是否与其它点连通
        bool flag=true;
        for(int j=1;j<=p;j++)
        {
            if(cnt[j])//有奶牛
            {
                if(dist[j]==0x3f3f3f3f)
                {
                    flag=false;
                    break;
                }
                //可以走到这个点
                sum+=cnt[j]*dist[j];
            }
        }
        if(flag)ans=min(ans,sum);
    }
    cout<<ans<<endl;
    return 0;
}

(4)最小花费(朴素djsl)

#include<bits/stdc++.h>
using namespace std;
#define ll long long 
#define pdi pair<double,int>
const int N=2000+10;
const int M=1e5+10;
double g[N][N];
int n,m,s,h;
double dist[N];//从起点到某点此时的剩余的钱为多少,取max
bool st[N];
void djsl()
{
    dist[s]=100;//假设初始100
    //迭代n次
    for(int i=1;i<=n;i++)
    {
        int t=-1;
        for(int j=1;j<=n;j++)
        {
            if((!st[j])&&(t==-1||dist[t]<dist[j]))t=j;
        }
        st[t]=1;
        for(int j=1;j<=n;j++)
        {
            dist[j]=max(dist[j],dist[t]*g[t][j]);
        }
    }
}
int main()
{
    cin>>n>>m;
    while(m--)
    {
        int x,y,z;
        cin>>x>>y>>z;
        double cc=(100.0-z)/100;//cc为可以得到的百分比
        g[x][y]=g[y][x]=max(g[x][y],cc);
    }
    cin>>s>>h;
    djsl();
   printf("%.8lf\n",100.0/dist[h]*100);
    return 0;
}
//扣除率=最后得到的钱/初始有的钱=dist[g]/100(先假设初始为100,先算出来,之后再用来求最终初始)
//初始有的钱=最后得到的钱/扣除率(根据题意,最后得到的钱为100)
//ans=100/(dist[g]/100)=100/dist[g]*100

(5)最优乘车(Floy三层循环)

输入一组一组数字(但是不先告诉个数)
#include<bits/stdc++.h>
using namespace std;
const int N=500+10;
const int M=100+10;
//数据量小可三层循环
int m,n;
//m条巴士线,n个站点
//直接枚举中间点Floy
int d[N][N];
//d[i][j]表示i到j的最小换乘次数
int main()
{
    cin>>m>>n;
    //输入每条巴士线经过的站点
    //输出最少换乘次数(无法达到输出-1)
    memset(d,0x3f,sizeof(d));
    getchar();
    while(m--)
    {
        string str;
        getline(cin,str);
        stringstream scin(str);
        int cnt=0,t,s[510];
        while(scin>>t)s[cnt++]=t;
        //假设同一条线上换乘次数都为1,最后答案减去1(乘第一条线不用换乘)
        for(int i=0;i<cnt;i++)
        {
            for(int j=i+1;j<cnt;j++)
            {
               d[s[i]][s[j]]=1;
            }
        }
    }
    for(int k=1;k<=n;k++)
    {
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=n;j++)
            {
                d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
            }
        }
    }
    if(d[1][n]==0x3f3f3f3f)cout<<-1<<endl;
    else cout<<d[1][n]-1<<endl;
    return 0;
}

(6)昂贵的聘礼(朴素djsl)

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=110;
//思路:终点为1号点,起点为虚拟源点S为0号点(表示直接买所需要的钱数)
//对于等级差距,可以枚举等级区间每次求一次S~1的最短路,取最小值即可
//邻接矩阵存图
int w[N][N],level[N]; 
//w[i][j]=cost表示从i花cost的钱到j
int m,n;
bool st[N];
int dist[N];
int djsl(int l,int r)
{
    memset(st,0,sizeof(st));
    memset(dist,0x3f,sizeof(dist));
    dist[0]=0;
    for(int i=1;i<=n;i++)
    {
        int t=-1;
        for(int j=0;j<=n;j++)//注意找最小花费时要包括虚拟源点0直接买的情况
        {
            if(!st[j]&&(t==-1||dist[t]>dist[j]))t=j;
        }
        st[t]=1;
        for(int j=1;j<=n;j++)
        {
            if(level[j]>=l&&level[j]<=r)
            {
                dist[j]=min(dist[j],dist[t]+w[t][j]);
            }
        }
    }
    return dist[1];
}
int main()
{
    memset(w,0x3f,sizeof(w));
    cin>>m>>n;
    //m为等级差距
    for(int i=1;i<=n;i++)w[i][i]=0;
    for(int i=1;i<=n;i++)
    {
        int price,cnt;
        cin>>price>>level[i]>>cnt;
        w[0][i]=min(w[0][i],price);
        for(int j=1;j<=cnt;j++)
        {
            int num,cost;
            cin>>num>>cost;
            w[num][i]=min(w[num][i],cost);
        }
    }
    int res=0x3f3f3f3f;
    //枚举等级区间
    //商家起始点在1
    for(int i=level[1]-m;i<=level[1]+m;i++)
    {
        res=min(res,djsl(i,i+m));
    }
    cout<<res<<endl;
    return 0;
}

2.单源最短路的综合应用

(1)新年好(djsl+dfs)

#include<bits/stdc++.h>
using namespace std;
#define pii pair<int,int> 
const int N=5e4+10;
const int M=2e5+10;
//1->2->3->4->5->6(数字表编号)
//即求(1->2)min+(2->3)min+(3->4)min+(4->5)min+(5->6)min
//1.先预处理出从1,a,b,c,d,e出发到其它所有点的单源最短路径
//2.DFS所有拜访顺序,对于每一种拜访顺序,可以通过查表的方式算出最短距离
int n,m;
int x1,x2,x3,x4,x5;
int h[N],e[M],ne[M],w[M],idx;
int dist[6][N];
//dist[i][j]表示以第i个亲戚为起点去往j处地(source[k]=j亲戚编号是k)的最短距离
bool st[N];
int source[6];
//source[i]中i表示亲戚编号,值表示亲戚家的居住地
void add(int a,int b,int c)
{
    e[idx]=b;
    w[idx]=c;
    ne[idx]=h[a];
    h[a]=idx++;
}
void djsl(int start,int dist[])
{
    memset(st,0,sizeof(st));
    memset(dist,0x3f3f3f3f,N);
    dist[start]=0;
    priority_queue<pii,vector<pii>,greater<>>heap;//小根堆
    heap.push({dist[start],start});
    while(heap.size())
    {
        auto t=heap.top();
        heap.pop();
        int u=t.second;
        if(st[u])continue;
        st[u]=1;
        for(int i=h[u];~i;i=ne[i])
        {
            int j=e[i];
            if(dist[j]>dist[u]+w[i])
            {
                dist[j]=dist[u]+w[i];
                heap.push({dist[j],j});
            }
        }
    }
}
// 枚举每种拜访次序,求出最小距离
// 拜访了u个人,自己是第1个人;当前起点是source[start],当前走过的距离是distance
// 当u为6时即拜访完
int dfs(int u,int start,int distance)
{
    if(u==6)return distance;
    int res=0x3f3f3f3f;
    for(int i=1;i<=5;i++)
    {
        if(!st[i])
        {
            int next=source[i];
            st[i]=1;//亲戚i已走
            res=min(res,dfs(u+1,i,dist[start][next]+distance));
            st[i]=0;
        }
    }
    return res;
}
int main()
{
    memset(h,-1,sizeof(h));
    cin>>n>>m;
    source[0]=1;
    for(int i=1;i<=5;i++)cin>>source[i];
    while(m--)
    {
        int x,y,t;
        cin>>x>>y>>t;
        add(x,y,t);
        add(y,x,t); 
    }
    for(int i=0;i<=5;i++)djsl(source[i],dist[i]);
    //dfs(u,start,distance)
    //表示拜访了u个人,自己是第一个人,当前起点为source[start](source[0]),当前走过的距离是distance
    memset(st,0,sizeof(st));
    cout<<dfs(1,0,0)<<endl;
    return 0;
}

(2)通信线路(spfa)

#include<bits/stdc++.h>
using namespace std;
const int N=1e4*3+100;
const int M=1100;
int n,m,k;
int e[N],h[N],ne[N],ver[N],idx;
int dist[M][M];
bool vis[M];
queue<int>q;
void add(int a,int b,int c)
{
    e[idx]=b;
    ver[idx]=c;
    ne[idx]=h[a];
    h[a]=idx++;
}
void spfa(int s)
{
    memset(dist,0x3f,sizeof(dist));
    dist[s][0]=0;
    vis[s]=1;
    q.push(s);
    while(q.size())
    {
        int t=q.front();
        q.pop();
        vis[t]=0;
        for(int i=h[t];~i;i=ne[i])
        {
            int j=e[i],z=ver[i],w=max(z,dist[t][0]);
            if(dist[j][0]>w)
            {
                dist[j][0]=w;
                if(!vis[j])
                {
                    q.push(j);
                    vis[j]=1;
                }
            }
            for(int p=1;p<=k;p++)
            {
                int w=min(dist[t][p-1],max(dist[t][p],z));
                if(dist[j][p]>w)
                {
                    dist[j][p]=w;
                    if(!vis[j])
                    {
                        q.push(j),vis[j]=1;
                    }
                }
            }
        }
    }
}
int main()
{
    memset(h,-1,sizeof(h));
    cin>>n>>m>>k;
    for(int i=1;i<=m;i++)
    {
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);
        add(b,a,c);
    }
    spfa(1);
    int ans=1e9;
    for(int i=0;i<=k;i++)
    {
        ans=min(ans,dist[n][i]);
    }
    if(ans==1e9)puts("-1");
    else cout<<ans<<endl;
    return 0;
}

(3)道路与航线(拓扑+djsl)

//道路:双向,边权非负(求最短路用djsl)
//航线:单向,边权可正可负,且无环
//(拓扑序:有向无环图,至少存在一个入度为0的点)
//单源最短路和拓扑序的结合
//如果边权非负,用djsl,O(mlogn)
//如果是拓扑图,不管边权正还是负,均可按拓扑序扫描,时间复杂度是线性的
//1.对于道路:输入所有双向道路,dfs出所有连通块,计算两个数组:id[]存储每个点属于哪个连通块;
//vector<int>block[]存下每一个连通块里的点
//2.对于航线:输入所有单向航线,同时统计出每个连通块的入度
//3.按照拓扑序依次处理每个连通块,先将所有入度为0的连通块的编号加入队列中,
//每次从队头取出一个连通块的编号bid,将block[bid]中的所有点加入堆中,
//然后对堆中所有点跑djsl算法。
//4.每次取出堆中最小的点ver,然后遍历ver的所有邻点j,如果id[ver]==id[j](在同一个连通块里),
//如果j能被更新,则将j插入堆中;如果id[ver]!=id[j](j在另外一个连通块中相当于访问到一条有向边),
//则将id[j]这个连通块的入度减1,如果减成0了,则将其插入拓扑排序的队列中。
//时间复杂度:O(m1logn+m2logn+m3logn+...)=O((m1+m2+m3+m4+...)logn)=O(mlogn)(m为边数,n为点数)
//单源最短路体现:dist[]初始化为正无穷,起点初始为dist[s]=0,从起点开始搜。

//道路:双向,边权非负(求最短路用djsl)
//航线:单向,边权可正可负,且无环
//(拓扑序:有向无环图,至少存在一个入度为0的点)
//单源最短路和拓扑序的结合
//如果边权非负,用djsl,O(mlogn)
//如果是拓扑图,不管边权正还是负,均可按拓扑序扫描,时间复杂度是线性的
//1.对于道路:输入所有双向道路,dfs出所有连通块,计算两个数组:id[]存储每个点属于哪个连通块;
//vector<int>block[]存下每一个连通块里的点
//2.对于航线:输入所有单向航线,同时统计出每个连通块的入度
//3.按照拓扑序依次处理每个连通块,先将所有入度为0的连通块的编号加入队列中,
//每次从队头取出一个连通块的编号bid,将block[bid]中的所有点加入堆中,
//然后对堆中所有点跑djsl算法。
//4.每次取出堆中最小的点ver,然后遍历ver的所有邻点j,如果id[ver]==id[j](在同一个连通块里),
//如果j能被更新,则将j插入堆中;如果id[ver]!=id[j](j在另外一个连通块中相当于访问到一条有向边),
//则将id[j]这个连通块的入度减1,如果减成0了,则将其插入拓扑排序的队列中。
//时间复杂度:O(m1logn+m2logn+m3logn+...)=O((m1+m2+m3+m4+...)logn)=O(mlogn)(m为边数,n为点数)
//单源最短路体现:dist[]初始化为正无穷,起点初始为dist[s]=0,从起点开始搜
#include<bits/stdc++.h>
using namespace std;
#define pii pair<int,int>
#define x first
#define y second
#define inf 0x3f3f3f3f
const int N=25010,M=150010;
int n,mr,mp,s;
int h[N],e[M],ne[M],w[M],idx;
int id[N];
vector<int>block[N];
int bcnt;//连通块个数
int dist[N];
bool st[N];
int din[N];//存下每个连通块的入度
queue<int>q;
void djsl(int bid);
void add(int a,int b,int c)
{
    e[idx]=b;
    w[idx]=c;
    ne[idx]=h[a];
    h[a]=idx++;
}
void dfs(int u,int bid)
{
    id[u]=bid;
    block[bid].push_back(u);
    //遍历当前u的所有邻边
    for(int i=h[u];~i;i=ne[i])
    {
        int j=e[i];
        if(!id[j])
        {
            dfs(j,bid);
        }
    }
}
void topsort()
{
    memset(dist,0x3f,sizeof(dist));
    dist[s]=0;
    //遍历所有连通块
    for(int i=1;i<=bcnt;i++)
    {
        if(!din[i])
        {
            q.push(i);
        }
    }
    while(q.size())
    {
        int t=q.front();
        q.pop();
        djsl(t);
    }
}
void djsl(int bid)
{
    priority_queue<pii,vector<pii>,greater<pii>>heap;
    //遍历每个堆中的所有点
    for(auto ver:block[bid])
    {
        heap.push({dist[ver],ver});
    }
    while(heap.size())
    {
        auto t=heap.top();
        heap.pop();
        int ver=t.y,distance=t.x;
        if(st[ver])continue;
        st[ver]=1;
        for(int i=h[ver];~i;i=ne[i])
        {
            int j=e[i];
            if(dist[j]>distance+w[i])
            {
                dist[j]=distance+w[i];
                if(id[j]==id[ver])
                {
                    heap.push({dist[j],j});
                }
            }
            if(id[j]!=id[ver]&&(--din[id[j]]==0))//入度减一同时做判断,即使判断无法进入也会减一
            {
                //id[j]的连通块入度为0
                q.push(id[j]);
            }
        }
    }
}
int main()
{
    cin>>n>>mr>>mp>>s;
    memset(h,-1,sizeof(h));
    while(mr--)
    {
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);
        add(b,a,c);
    }
    //暴搜所有连通块
    for(int i=1;i<=n;i++)
    {
        if(!id[i])
        {
            dfs(i,++bcnt);
        }
    }
    while(mp--)
    {
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);
        din[id[b]]++;
        //b所在的连通块入度加一
    }
    topsort();
    for(int i=1;i<=n;i++)
    {
        if(dist[i]>inf/2)//不一定是正无穷,因为有负权边,大于一个大数即可
        {
            puts("NO PATH");
        }
        else cout<<dist[i]<<endl;

    }
    return 0;
}

(4)最优贸易(spfa正向求最小和反向求最大)

#include<bits/stdc++.h>
using namespace std;
//所有从1到N先买再卖的最大收益
//按照分界点分类(分界点是在某点买入或者卖出)
//设k为分界点
//从1到k买入的最小值dmin(k)
//从k到n卖出的最大值dmax(k)
//差价:dmax(k)-dmin(k)
//s1->k<-s2    dmin(k)=min{dmin(s1),dmin(s2),...,wk}
//即求最短路(用spfa求dmin的值)(dmax用反向图从n号点出发求最大值)
//Bellmax-ford(dp):for迭代n-1次,在for每条边,用三角不等式更新
const int N=1e5+10;
const int M=2e6+10;
int n,m;
int w[N];//每个水晶球价格
int hs[N],ht[N],e[M],ne[M],idx;
//hs正向图,ht反向图
int dmin[N],dmax[N];
bool st[N];
int q[N];//队列spfa
void add(int h[],int a,int b)
{
    e[idx]=b;ne[idx]=h[a];h[a]=idx++;
}
void spfa(int h[],int dist[],int type)
{
    int hh=0,tt=1;
    if(type==0)//求最小值
    {
        memset(dist,0x3f,sizeof(dmin));
        dist[1]=w[1];
        q[0]=1;
    }
    else//最大值    
    {   
        memset(dist,-0x3f,sizeof(dmax));
        dist[n]=w[n];
        q[0]=n;
    }
    while(hh!=tt)
    {
        int t=q[hh++];//取出队头
        if(hh==N)hh=0;
        st[t]=0;
        for(int i=h[t];~i;i=ne[i])
        {
            int j=e[i];
            if((type==0&&dist[j]>min(dist[t],w[j]))||(type==1&&dist[j]<max(dist[t],w[j])))
            {
                if(type==0)dist[j]=min(dist[t],w[j]);
                else dist[j]=max(dist[t],w[j]);
                if(!st[j])
                {
                    q[tt++]=j;
                    if(tt==N)tt=0;
                    st[j]=1;
                }
            }
        }
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)scanf("%d",&w[i]);
    memset(hs,-1,sizeof(hs));
    memset(ht,-1,sizeof(ht));
    while(m--)
    {
        int a,b,c;
        cin>>a>>b>>c;
        add(hs,a,b),add(ht,b,a);//单向道路的正向图和反向图
        if(c==2)add(hs,b,a),add(ht,a,b);//双向道路的另外一条边的正向图和反向图
    }
    spfa(hs,dmin,0);
    spfa(ht,dmax,1);
    //枚举所有点为分界点求最大值
    int res=0;
    for(int i=1;i<=n;i++)
    {
        res=max(res,dmax[i]-dmin[i]);
    }
    cout<<res<<endl;
    return 0;
}

3.单源最短路的扩展应用

(1)选择最佳线路(spfa多个起点到一个终点-反向-多个终点到一个起点)

#include<bits/stdc++.h>
using namespace std;
const int N=1000+10;
const int M=200000+10;
//起点不确定,但是终点确定求最短路
//可以建反向图,把终点当做起点,再把多个起点当成多个终点枚举求最短路
int n,m,s;
//n:点数  m:边数  s:终点
int h[N],e[M],ne[M],w[M],idx;
int start[N];
int scnt;
bool st[N];
int d[N];
//d[x]表示s到x的最短距离
void add(int a,int b,int c)
{
    e[idx]=b;
    w[idx]=c;
    ne[idx]=h[a];
    h[a]=idx++;
}
void spfa()
{
    memset(d,0x3f3f3f3f,sizeof(d));
    memset(st,0,sizeof(st));
    d[s]=0;
    st[s]=1;
    queue<int>q;
    q.push(s);
    while(q.size())
    {
        auto t=q.front();
        q.pop();
        st[t]=0;
        for(int i=h[t];~i;i=ne[i])
        {
            int j=e[i];
            if(d[j]>d[t]+w[i])
            {
                d[j]=d[t]+w[i];
                if(!st[j])
                {
                    st[j]=1;
                    q.push(j);
                }
            }
        }
    }
}
int main()
{
    while(cin>>n>>m>>s)
    {
        memset(h,-1,sizeof(h));
        while(m--)
        {
            int a,b,c;
            cin>>a>>b>>c;
            add(b,a,c);
        }
        spfa();
        //只做一遍spfa求起点s到各个点的距离
        cin>>scnt;
        int res=0x3f3f3f3f;
        for(int i=0;i<scnt;i++)
        {
            int x;
            cin>>x;
            res=min(res,d[x]);
        }
        if(res==0x3f3f3f3f)cout<<-1<<endl;
        else cout<<res<<endl;
    }
    return 0;
}

4.Floyd算法

(1)牛的旅行

#include<bits/stdc++.h>
using namespace std;
//用floyd算法求出任意两点之间的最短距离
//求maxd[i],表示和i连通的且距离i最远的点的距离
//情况1:所有maxd[i]的最大值(已连通)
//情况2:枚举在哪两个点之间连边。i,j需要满足d[i,j]=inf
//       maxd[i]+dist[i,j]+maxd[j]
#define pii pair<int,int>
const int N=150;
const double inf=1e20;
int n;
pii q[N];
char g[N][N];
double d[N][N];
double maxd[N];
double get_dist(pii a,pii b)
{
    double dx=a.first-b.first;
    double dy=a.second-b.second;
    return sqrt(dx*dx+dy*dy);
}
int main()
{
    cin>>n;
    for(int i=0;i<n;i++)
    {
        cin>>q[i].first>>q[i].second;
    }
    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)
            {
                if(g[i][j]=='1')d[i][j]=get_dist(q[i],q[j]);
                else d[i][j]=inf;
            }
        }
    }
    //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]=min(d[i][j],d[i][k]+d[k][j]);
            }
        }
    }
    for(int i=0;i<n;i++)
    {
        for(int j=0;j<n;j++)
        {
            if(d[i][j]<inf)
            {
                //求maxd[i],表示和i连通的且距离i最远的点的距离
                maxd[i]=max(maxd[i],d[i][j]);
            }
        }
    }
    double res1=0;
    for(int i=0;i<n;i++)res1=max(res1,maxd[i]);
    double res2=inf;
    for(int i=0;i<n;i++)
    {
        for(int j=0;j<n;j++)
        {
            if(d[i][j]>=inf)
            {
                res2=min(res2,get_dist(q[i],q[j])+maxd[i]+maxd[j]);
            }
        }
    }
    printf("%.6lf\n",max(res1,res2));
    return 0;
}

(2)排序

#include<bits/stdc++.h>
using namespace std;
//传递闭包(floyd)
//d[i,j]=1,d[i,j]=0
//1.矛盾,d[i,j]=1
//2.唯一确定(i!=j),d[i,j]和d[j,i]一个为1(不断求出未被标记过的最小值)
//3.顺序不唯一
//迭代次数t:m组关系中到第t组关系能够出现三种情况中的一种
//(1)判断一个人的名次是否确定:比自己成绩好的人数+比自己成绩差的人数=除自己外的总人数
//(2)根据m组关系,如何判断一个人是否比另一个人的成绩好->
//用floyd:判断两点之间是否可以到达(dist[x][y]!=1e9->x的成绩比y好)
//对于一个点,遍历其余所有点就可以得到:比该点成绩好的点数和比该点成绩差的点数
//(3)判断矛盾:从 x 能到达 y 并且从 y 也能到达 x(即,x 比 y 成绩好并且 y 比 x 成绩好),那就是出现矛盾了。
//(4)输出排好的序列:将所有点按照能到达的点数从小到大排序,然后依次输出编号。
#define pii pair<int,int>
const int N=210;
int T,n,m,k;
int a[N],dist[N][N];
pii ans[N];
void floyd(int x)//以x为中转点,更新其它点
{
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            dist[i][j]=min(dist[i][j],dist[i][x]+dist[x][j]);
        }
    }
}
bool pd()//判断所有点是否确定
{
    for(int i=1;i<=n;i++)
    {
        int cnt=0;
        for(int j=1;j<=n;j++)
        {
            if(dist[i][j]!=1e9)cnt++;
        }
        ans[i]={cnt,i};//i能到达的所有点数(i比这些人成绩好)
        for(int j=1;j<=n;j++)
        {
            if(i!=j&&dist[j][i]!=1e9)cnt++;//能到达当前点的数
        }
        if(cnt!=n)return 0;//名次无法确定
    }
    sort(ans+1,ans+n+1);
    return 1;//名次唯一确定
}
int main()
{
    while(cin>>n>>m&&n)
    {
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=n;j++)
            {
                if(i!=j)dist[i][j]=1e9;
            }
        }
        bool flag=0;
        for(int i=1;i<=m;i++)
        {
            char a,t,b;
            cin>>a>>t>>b;
            int x=a-'A'+1;
            int y=b-'A'+1;
            //y>x即加一条y到x的边
            //如果已有x到y,就矛盾了
            if(!flag&&dist[x][y]!=1e9)
            {
                printf("Inconsistency found after %d relations.\n", i);
                flag=1;
            }
            dist[y][x]=1;
            floyd(x),floyd(y);//分别以x,y为中转点更新其它点
            if(!flag&&pd())//所有点已确定
            {
                printf("Sorted sequence determined after %d relations: ", i);
                for(int i=1;i<=n;i++)
                {
                    cout<<char(ans[i].second+'A'-1);
                }
                cout<<".\n";
                flag=1;
            }
        }
        if(!flag) printf("Sorted sequence cannot be determined.\n"); //无法确定 
    }
    return 0;
}

(3)观光之旅

#include<bits/stdc++.h>
using namespace std;
//无向图的最小环问题(Floyd)
//<->i<->k<->j<->最短路径长为:d[i,j]+w[i,k]+w[k,j]
//d[i,j]=d[i,k]+d[k,j]其中k为i到j中编号最大的一个点
//floyd:for(k:for(i:for(j)))当在k时,即是d[i,j]i->j只经过(1~(k-1))这么多个点的最短路径
//最小环:至少包含3个点,环上的节点不重复,环上的边的长度之和最小
const int N=100+10;
const int M=1e4+10;
int n,m;
int d[N][N],g[N][N];
int p[N][N];
int path[N],cnt;
void getpath(int i,int j)
{
    //i和j本身为一条边
    if(p[i][j]==0)return;
    int k=p[i][j];
    getpath(i,k);
    path[cnt++]=k;
    getpath(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);
    }
    memcpy(d,g,sizeof(d));
    int res=0x3f3f3f3f;
    for(int k=1;k<=n;k++)
    {
        //求最小环,枚举i和j,且k是i,j上最大的编号
        for(int i=1;i<k;i++)
        {
            for(int j=i+1;j<k;j++)
            {
                //因为g初始很大可能爆int,需要转成longlong
                if((long long)d[i][j]+g[j][k]+g[k][i]<res)
                {
                    res=d[i][j]+g[j][k]+g[k][i];
                    cnt=0;
                    path[cnt++]=k;
                    path[cnt++]=i;
                    getpath(i,j);
                    path[cnt++]=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];
                    p[i][j]=k;//转移中间点
                }
            }
        }
    }
    if(res==0x3f3f3f3f)puts("No solution.\n");
    else
    {
        for(int i=0;i<cnt;i++)
        {
            cout<<path[i]<<" ";
        }
        cout<<endl;
    }
    return 0;
}

5.最小生成树

最小生成树构成后所有的点都被连通一共有n-1条边。(n个点)
而最短路只要从起始点到达目的地走的是最短的路径即可,与所有的点连不连通没有关系。

Kruskal算法可以很好地利用并查集来实现其核心思想,即判断边的两个节点是否在同一个集合中,从而避免形成回路。

在并查集中,每个元素都属于一个集合,并且每个集合有一个代表元素。初始时,每个节点都是一个独立的集合,即每个节点的代表元素是其自身。

Kruskal算法利用并查集的步骤如下:

初始化并查集,将图中的每个节点都作为一个独立的集合,即每个节点的代表元素是其自身。
将图中的所有边按照权重从小到大进行排序。
依次考虑排序后的每条边。
对于当前考虑的边(u, v),查找u和v的代表元素,如果它们的代表元素不同,说明u和v不在同一个集合中,将这条边(u, v)加入最小生成树,并将u和v所在的集合合并(即将其中一个代表元素指向另一个代表元素)。
重复步骤3和步骤4,直到最小生成树中包含了所有的节点(边的数量等于节点数减1)。
通过并查集的合并操作,Kruskal算法能够高效地判断两个节点是否在同一个集合中,从而避免形成回路,并构建出最小生成树。当所有的边都考虑完毕时,最小生成树就构建完成了。

(1)最短网络(kruscal)

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
//连接起所有点的最小总长度(最小生成树)
struct node
{
    int u,v,w;
    bool operator<(const node &W)const
    {
        return w<W.w;
    }
}s[N];
int p[N];//并查集的parent数组
int n,m;
int ans;
int find(int x)
{
    if(p[x]==x)return p[x];
    else return p[x]=find(p[x]);
}
void merge(int x,int y)
{
    int px=find(x);
    int py=find(y);
    p[px]=py;
}
void kruscal()
{
    for(int i=1;i<=n;i++)p[i]=i;
    sort(s+1,s+1+m);
    int cnt=n-1;//边数
    for(int i=1;i<=m;i++)
    {
        int u=s[i].u;
        int v=s[i].v;
        int w=s[i].w;
        if(find(u)==find(v))continue;
        //不在同一个集合
        merge(u,v);
        cnt--;
        ans+=w;
        if(cnt==0)break;
    }
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            int aa;
            cin>>aa;
            m++;//边数++
            s[m]={i,j,aa};
        }
    }
    kruscal();
    cout<<ans<<endl;
    return 0;
}

(2)局部网

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
//求出最小生成树的边权和ans,再用边权总和sum减去ans即为答案
int n,k;
struct node
{
    int u,v,w;
    bool operator<(const node &W)const
    {
        return w<W.w;
    }
}s[N];
int p[N];
int ans;
int sum;
int find(int x)
{
    if(p[x]==x)return p[x];
    else return p[x]=find(p[x]);
}
void merge(int u,int v)
{
    int pu=find(u);
    int pv=find(v);
    p[pu]=pv;
    return;
}
void kruscal()
{
    for(int i=0;i<=n;i++)p[i]=i;
    sort(s+1,s+k+1);
    int cnt=n-1;//全部点连通
    for(int i=1;i<=k;i++)
    {
        int u=s[i].u;
        int v=s[i].v;
        int w=s[i].w;
        if(find(u)==find(v))continue;
        merge(u,v);
        cnt--;
        ans+=w;
        if(cnt==0)break;
    }
}
int main()
{
    cin>>n>>k;
    for(int i=1;i<=k;i++)
    {
        int u,v,w;
        cin>>u>>v>>w;
        s[i]={u,v,w};
        sum+=w;
    }
    kruscal();
    cout<<sum-ans<<endl;
    return 0;
}

(3)繁忙的都市

 

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
const int M=8000+10;
int n,m;
struct node
{
    int u,v,w;
    bool operator<(const node &W)const
    {
        return w<W.w;
    }
}s[N];
int p[N];
int res;
int find(int x)
{
    if(p[x]==x)return p[x];
    else return p[x]=find(p[x]);
}
void merge(int u,int v)
{
    int pu=find(u);
    int pv=find(v);
    p[pu]=pv;
}
void kruscal()
{
    for(int i=0;i<=n;i++)p[i]=i;
    sort(s+1,s+1+m);
    int cnt=n-1;
    for(int i=1;i<=m;i++)
    {
        int u=s[i].u;
        int v=s[i].v;
        int w=s[i].w;
        if(find(u)==find(v))continue;
        merge(u,v);
        cnt--;
        res=w;//操作到后面排好序的最大一条边即为最大边权值(且由于最小生成树已保证最小)
        if(cnt==0)break;
    }
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=m;i++)
    {
        int u,v,w;
        cin>>u>>v>>w;
        s[i]={u,v,w};
    }
    kruscal();
    cout<<n-1<<" "<<res<<endl;
    return 0;
}

 (4)联络员

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
struct node
{
    int u,v,w;
    bool operator<(const node &W)const
    {
        return w<W.w;
    }
}s[N];
int n,m;
int res;
int p[N];
int find(int x)
{
    if(p[x]==x)return p[x];
    else return p[x]=find(p[x]);
}
void merge(int u,int v)
{
    int pu=find(u);
    int pv=find(v);
    p[pu]=pv;
}
void kruscal()
{
    sort(s+1,s+1+m);
    for(int i=1;i<=m;i++)
    {
        int u=s[i].u;
        int v=s[i].v;
        int w=s[i].w;
        if(find(u)==find(v))continue;
        merge(u,v);
        res+=w;
    }
}
int main()
{
    cin>>n>>m;
    for(int i=0;i<=n;i++)p[i]=i;//初始要放主函数里
    for(int i=1;i<=m;i++)
    {
        int pp,u,v,w;
        cin>>pp>>u>>v>>w;
        s[i]={u,v,w};
        //若必选直接连边相加
        if(pp==1)
        {
            merge(u,v);
            res+=w;
        }
    }
    kruscal();
    cout<<res<<endl;
    return 0;
}

(5)连接格点

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int n,m;//n行m列
//二维坐标转一维编号[x,y]
//num=(x-1)*n+y;
int p[N];
int num;//总编号
int res;
int find(int x)
{
    if(p[x]==x)return p[x];
    else return p[x]=find(p[x]);
}
void merge(int u,int v)
{
    int pu=find(u);
    int pv=find(v);
    p[pu]=pv;
}
void kruscal()
{
    //纵向合并花费一金币,先合并
    for(int j=1;j<=m;j++)
    {
        for(int i=1;i<n;i++)
        {
            int u=(i-1)*n+j;
            int v=i*n+j;//i不能为n,当i==n-1时这里已经代表第n行
            if(find(u)==find(v))continue;
            merge(u,v);
            res++;
        }
    }
    //横向合并花费2金币,后合并
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<m;j++)
        {
            int u=(i-1)*n+(j+1);//j不能为m,当j==m-1时这里已经代表第m列
            int v=(i-1)*n+j;
            if(find(u)==find(v))continue;
            merge(u,v);
            res+=2;
        }
    }
}
int main()
{
    cin>>n>>m;
    num=n*m;
    for(int i=0;i<=num;i++)p[i]=i;
    int x1,y1,x2,y2;
    while(cin>>x1>>y1>>x2>>y2)
    {
       int idx1=(x1-1)*n+y1;
       int idx2=(x2-1)*n+y2;
       merge(idx1,idx2);
    }
    kruscal();
    cout<<res<<endl;
    return 0;
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值