图论——最短路&最小生成树

经验

  1. g,h,dist,st,p(并查集)这些数组记得初始化,根据情况初始化
  2. g数组加边的时候记得min一下
  3. dijkstra朴素版NN,堆优化版mlogm,bellman(n*m),spfa一般m,floyd(nnn)

单源最短路的建图方式

热浪

原题链接

这里是引用

题意:给定T个点,C条无向边,求从Ts到Tc的最短路
思路:用朴素版dijkstra就可以求出来。

代码:

#include<iostream>
#include<algorithm>
#include<cmath>
#include<string.h>
#include<string>
#include<vector>
#include<queue>
#include<set>
#include<map>
#define int long long
#define fi first
#define se second
#define inf 0x3f3f3f3f
using namespace std;
typedef pair<int, int>pii;
const int N =2520,M=2*6210;
int S,T,n,m;
int h[N],w[M],e[M],ne[M],idx;
signed int dist[N];
bool st[N];

void add(int a,int b,int c){
    e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}

int dj(){
    memset(dist,0x3f,sizeof dist);
    dist[S]=0;
    for(int k=0;k<n;k++){
        int t=-1;
        for(int i=1;i<=n;i++){
            if(!st[i]&&(t==-1||dist[t]>dist[i]))
                t=i;
        }
        st[t]=true;
        for(int i=h[t];~i;i=ne[i]){
            int j=e[i];
            if(dist[j]>dist[t]+w[i])
                dist[j]=dist[t]+w[i];
        }
    }
    return dist[T];
}

signed main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin>>n>>m>>S>>T;
    memset(h,-1,sizeof h);
    while(m--){
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);
        add(b,a,c);
    }
    cout<<dj();
    return 0;
}

信使

原题链接

这里是引用

题意:一个n个点,m条边的无向图,求从源点1到各个点的最短路的最大值。
代码:dijkstra就可以解决。如果用g数组,初始化加边的时候记得min一下。

代码:

#include<iostream>
#include<algorithm>
#include<cmath>
#include<string.h>
#include<string>
#include<vector>
#include<queue>
#include<set>
#include<map>
#define int long long
#define fi first
#define se second
#define inf 0x3f3f3f3f3f3f3f3f//注意inf
using namespace std;
typedef pair<int, int>pii;

const int N = 110,M=400;
int h[N],e[M],ne[M],w[M],idx;
int dist[N];
bool st[N];
int ans=0;
int n,m;
void add(int a,int b,int c){
    e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
void dj(){
    
    memset(dist,0x3f,sizeof dist);
    memset(st,0,sizeof st);
    dist[1]=0;
    for(int k=0;k<n;k++){
        int t=-1;
        for(int i=1;i<=n;i++){
            if(!st[i]&&(t==-1||dist[t]>dist[i]))
                t=i;
        }
        st[t]=true;
        for(int i=h[t];~i;i=ne[i]){
            int j=e[i];
            if(!st[j]&&dist[j]>dist[t]+w[i])
                dist[j]=dist[t]+w[i];
        }
    }
}

signed main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin>>n>>m;
    memset(h,-1,sizeof h);
    while(m--){
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);
        add(b,a,c);
    }
    dj();
    for(int i=2;i<=n;i++){
        ans=max(ans,dist[i]);
    }
    if(ans==inf) ans=-1;
    cout<<ans;
    return 0;
}

香甜的黄油

原题链接

这里是引用
题意:p个点c条边的无向图,求以某个点为终点,n头牛所在牧场到终点距离之和最小。
思路:反向思维,枚举每个点为源点,然后作spfa,求到每个牛所在牧场的距离之和的最小值,即每个点最短路相加。

代码:

#include<iostream>
#include<algorithm>
#include<cmath>
#include<string.h>
#include<string>
#include<vector>
#include<queue>
#include<set>
#include<map>
#define int long long
#define fi first
#define se second
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
typedef pair<int, int>pii;

const int N =810,M=3000;
int h[N],e[M],ne[M],w[M],idx;
int dist[N];
bool st[N];
int ans=inf;
int n,m,k;
int a[N];
void add(int a,int b,int c){
    e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}

int spfa(int s){//一般复杂度为O(m),用dj会超时
    
    memset(dist,0x3f,sizeof dist);
    memset(st,0,sizeof st);
    // queue<int> q;
    // q.push(s);
    // dist[s]=0;
    // st[s]=true;
    // while(q.size()){
    //     int t=q.front();
    //     q.pop();
    //     st[t]=false;
    //     for(int i=h[t];~i;i=ne[i]){
    //         int j=e[i];
    //         if(dist[j]>dist[t]+w[i]){
    //             dist[j]=dist[t]+w[i];
    //             if(!st[j]){
    //                 q.push(j);
    //                 st[j]=true;
    //             }
    //         }
    //     }
    // }
    
    //手写循环队列,注意进出队列以及都需要判断。
    //若tt=0,hh=0,初始化,hh++,tt++、因为tt始终指向最后一个元素上面位置。循环队列
    //若tt=-1,hh=0初始化,hh++,++tt、因为tt始终指向最后一个元素。普通队列
    int q[N];
    int hh=0,tt=1;
    q[0]=s;
    dist[s]=0;
    st[s]=true;
    while(hh!=tt){
        int t=q[hh++];
        if(hh==N) hh=0;
        st[t]=false;
        for(int i=h[t];~i;i=ne[i]){
            int j=e[i];
            if(dist[j]>dist[t]+w[i]){
                dist[j]=dist[t]+w[i];
                if(!st[j]){
                    q[tt++]=j;
                    if(tt==N) tt=0;
                    st[j]=true;
                }
            }
        }
    }
    
    
    int sum=0;
    for(int i=0;i<k;i++){
        if(dist[a[i]]==inf) return inf;
        sum+=dist[a[i]];
    }
    return sum;
}

signed main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    memset(h,-1,sizeof h);
    cin>>k>>n>>m;
    for(int i=0;i<k;i++) cin>>a[i];
    
    while(m--){
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);
        add(b,a,c);
    }
    
    for(int i=1;i<=n;i++){
        ans=min(ans,spfa(i));
    }

    cout<<ans;
    return 0;
}

最小花费

原题链接

这里是引用
题意:求一个数经过边权相乘传递后最大值
思路:即求1w1w2…wn最大值,所以本题是一个求最大路。注意dist和g的初始化,要初始化小的

代码:

#include<iostream>
#include<algorithm>
#include<cmath>
#include<string.h>
#include<string>
#include<vector>
#include<queue>
#include<set>
#include<map>
#define int long long
#define fi first
#define se second
#define inf 0x3f3f3f3f
using namespace std;
typedef pair<int, int>pii;

const int N = 2010;
int n,m,s,t;
double g[N][N];
bool st[N];
double dist[N];

void dj(){
    dist[s]=1;//求最大注意不再是初始化为0了
    for(int k=0;k<n;k++){
        int t=-1;
        for(int i=1;i<=n;i++){
            if(!st[i]&&(t==-1||dist[t]<dist[i]))
                t=i;
        }
        st[t]=true;
        for(int i=1;i<=n;i++)
            dist[i]=max(dist[i],dist[t]*g[t][i]);
    }
    
}
signed main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin>>n>>m;
    while(m--){
        int a,b;
        double c;
        cin>>a>>b>>c;
        c=(100-c)/100;
        g[b][a]=g[a][b]=max(g[a][b],c);//双向边,注意了不然会wc
    }
    cin>>s>>t;
    dj();
    printf("%.8lf",100.0/dist[t]);
    return 0;
}

最优乘车

原题链接

这里是引用

题意:一共m条路线,求点1到点n最少换乘次数
思路:m条线路,每条线路转换成权为1的传递路线,最后求最短路-1.
总结:本题运用了stringstream流进行输入。

#include<iostream>
#include<algorithm>
#include<sstream>
#include<cmath>
#include<string.h>
#include<string>
#include<vector>
#include<queue>
#include<set>
#include<map>
#define int long long
#define fi first
#define se second
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
typedef pair<int, int>pii;

const int N =550;
bool g[N][N];
bool st[N];
int dist[N];
int n,m;
int id[N];

// void dj(){
//     memset(dist,0x3f,sizeof dist);
//     dist[1]=0;
//     for(int k=0;k<n;k++){
//         int t=-1;
//         for(int i=1;i<=n;i++){
//             if(!st[i]&&(t==-1||dist[t]>dist[i]))
//                 t=i;
//         }
//         st[t]=true;
//         for(int i=1;i<=n;i++)
//             dist[i]=min(dist[i],dist[t]+g[t][i]);
//     }
// }

void dfs(int u){
    if(u==n) return;
    for(int i=1;i<=n;i++){
        if(g[u][i]&&(dist[i]>dist[u]+1)){
            dist[i]=dist[u]+1;
            dfs(i);
        }
    }
}

signed main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin>>m>>n;
    // memset(g,0x3f,sizeof g);
    for(int i=1;i<=n;i++) g[i][i]=1;
    string line;
    getline(cin,line);
    while(getline(cin,line)){
        stringstream ss(line);//流的运用
        int p,cnt=0;
        while(ss>>p) id[cnt++]=p;//getline(ss,p,',')
        for(int i=0;i<cnt;i++){
            for(int j=i+1;j<cnt;j++){
                g[id[i]][id[j]]=true;//这里注意,不在是i,j
            }
        }
    }
    // dj();
    // if(dist[n]==inf) cout<<"NO"<<endl;
    // else cout<<dist[n]-1;
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;
    dfs(1);
    if(dist[n]==inf) cout<<"NO"<<endl;
    else cout<<dist[n]-1;
    return 0;
}

昂贵的聘礼

原题链接

这里是引用

题意:给定n个点,每个点有等级和权值,然后每个点有x条边,给出边权和终点,求到终点1的最短路。
思路:将每个点的权值转化为一个超级源点到每个点的边权,最后就是一个从超级源点到终点1的最短路问题。

代码:

#include<iostream>
#include<algorithm>
#include<cmath>
#include<string.h>
#include<string>
#include<vector>
#include<queue>
#include<set>
#include<map>
#define int long long
#define fi first
#define se second
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
typedef pair<int, int>pii;
//本题重点是构造虚拟源点
const int N = 110;
int g[N][N];
int dist[N];
bool st[N];
int level[N];
int n,m;

int dj(int l,int r){
    memset(dist,0x3f,sizeof dist);
    memset(st,0,sizeof st);
    dist[0]=0;
    for(int k=0;k<=n;k++){
        int t=-1;
        for(int i=0;i<=n;i++){
            if(!st[i]&&(t==-1||dist[t]>dist[i]))
                t=i;
        }
        st[t]=true;
        for(int i=0;i<=n;i++){
            if(level[i]>=l&&level[i]<=r)
                dist[i]=min(dist[i],dist[t]+g[t][i]);
        }
    }
    return dist[1];
}
signed main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin>>m>>n;
    memset(g,0x3f,sizeof g);
    for(int i=0;i<=n;i++) g[i][i]=0;
    for(int i=1;i<=n;i++){
        int p,cnt;
        cin>>p>>level[i]>>cnt;
        g[0][i]=p;
        while(cnt--){
            int t,v;
            cin>>t>>v;
            g[t][i]=min(g[t][i],v);
        }
    }
    int ans=inf;
    for(int i=level[1]-m;i<=level[1];i++) ans=min(ans,dj(i,i+m));
    cout<<ans;
    return 0;
}

单源最短路的综合应用

新年好

原题链接

这里是引用

题意:求从源点1,经过五个点的最短路。无向图
思路:以源点1,和这五个点分别作源点求最短路并且保存下来,然后全排列求最短路

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>

#define int long long 
using namespace std;

typedef pair<int, int> PII;

const int N = 50010, M = 200010, INF = 0x3f3f3f3f3f3f3f3f;

int n, m;
int h[N], e[M], w[M], ne[M], idx;
int q[N], dist[6][N];
int source[6];
bool st[N];

void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}

void dijkstra(int start, int dist[])
{
    memset(dist, 0x3f, N * 8);//用long long记得是八个字节
    dist[start] = 0;
    memset(st, 0, sizeof st);

    priority_queue<PII, vector<PII>, greater<PII>> heap;
    heap.push({0, start});

    while (heap.size())
    {
        auto t = heap.top();
        heap.pop();

        int ver = t.second;
        if (st[ver]) continue;
        st[ver] = true;

        for (int i = h[ver]; ~i; i = ne[i])
        {
            int j = e[i];
            if (dist[j] > dist[ver] + w[i])
            {
                dist[j] = dist[ver] + w[i];
                heap.push({dist[j], j});
            }
        }
    }
}

int dfs(int u, int start, int distance)
{
    if (u > 5) return distance;

    int res = INF;
    for (int i = 1; i <= 5; i ++ )
        if (!st[i])
        {
            int next = source[i];
            st[i] = true;
            res = min(res, dfs(u + 1, i, distance + dist[start][next]));
            st[i] = false;
        }

    return res;
}


signed main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin>>n>>m;
    source[0] = 1;
    
    for(int i=1;i<=5;i++) cin>>source[i];

    memset(h, -1, sizeof h);
    while (m -- )
    {
        int a, b, c;
        cin>>a>>b>>c;
        add(a, b, c), add(b, a, c);
    }

    for (int i = 0; i < 6; i ++ ) dijkstra(source[i], dist[i]);

    memset(st, 0, sizeof st);
    cout<<dfs(1,0,0);
    return 0;
}

通信线路

原题链接

这里是引用

题意:从源点1到终点n,如果路线长度小于k,不花钱为0.如果路线长度大于k条,则求k+1大的花费最小。
思路:二分答案,最小化答案r=mid,然后花费大于x的权值为1,否则为0,变成一个双端队列最短路问题。

代码:

#include<iostream>
#include<algorithm>
#include<cmath>
#include<string.h>
#include<string>
#include<vector>
#include<queue>
#include<set>
#include<map>
#define int long long
#define fi first
#define se second
#define inf 0x3f3f3f3f
using namespace std;
typedef pair<int, int>pii;

const int N = 1100,M=2e4+30;
int n,m,k;
int dist[N];
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++;
}
bool check(int x){
    memset(dist,0x3f,sizeof dist);
    memset(st,0,sizeof st);
    dist[1]=0;
    // priority_queue<pii,vector<pii>,greater<pii> > q;
    deque<pii> q;
    q.push_back({0,1});
    while(q.size()){
        auto t=q.front();
        q.pop_front();
        int ver=t.se;
        if(st[ver]) continue;
        st[ver]=true;
        for(int i=h[ver];~i;i=ne[i]){
            int j=e[i];
            int d=w[i]>x;
            if(dist[j]>dist[ver]+d){
                dist[j]=dist[ver]+d;
                if(d) q.push_back({dist[j],j});
                else q.push_front({dist[j],j});
            }
        }
    }
    return dist[n]<=k;
}
signed main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin>>n>>m>>k;
    memset(h,-1,sizeof h);
    while(m--){
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c),add(b,a,c);
    }
    
    int l=-1,r=1e6+1;//当最短路径小于k时,经费为0.当经费为r时,表示通不过
    //l+1==r时,停止二分。所以l  区间[-1,1e6],r  [0,1e6+1];
    while(l+1<r){
        int mid=l+r>>1;
        if(check(mid)) r=mid;
        else l=mid;
    }
    if(r==1e6+1) r=-1;
    cout<<r;
    return 0;
}

道路与航线

原题链接

这里是引用

题意:求从源点s到所有点距离之和的最小值
思路:求有负权边的最短路,用spfa可能会被卡,可以用双端队列进行优化。最优做法是用dijkstra加拓扑序列。
步骤:
1.将所有正权无向边相连能联通的点进行缩点(联通团),然后加负权边,更新联通团的入度。
2.将联通团入度为0的团加入拓扑队列,然后队列不空出队头(是一个联通团)
3.将入度为0的联通团里的所有点加入堆中,进行堆优化dijkstra。不在同一个团的点,让其团入度减1,如果减到0,加入拓扑队列中;更新距离,如果在一个团中的点加入堆中。

代码1:拓扑序列加dijkstra

#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>

#define x first
#define y second
#define int long long
using namespace std;

typedef pair<int, int> PII;

const int N = 25010, M = 150010, INF = 0x3f3f3f3f;

int n, mr, mp, S;
int id[N];
int h[N], e[M], w[M], ne[M], idx;
int dist[N], din[N];
vector<int> block[N];
int bcnt;
bool st[N];
queue<int> q;

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

    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if (!id[j])
            dfs(j, bid);
    }
}

void dijkstra(int bid)
{
    priority_queue<PII, vector<PII>, greater<PII>> heap;

    for (auto u : block[bid])
        heap.push({dist[u], u});

    while (heap.size())
    {
        auto t = heap.top();
        heap.pop();

        int ver = t.y, distance = t.x;
        if (st[ver]) continue;
        st[ver] = true;

        for (int i = h[ver]; ~i; i = ne[i])
        {
            int j = e[i];
            if (id[j] != id[ver] && -- din[id[j]] == 0) q.push(id[j]);
            if (dist[j] > dist[ver] + w[i])
            {
                dist[j] = dist[ver] + w[i];
                if (id[j] == id[ver]) heap.push({dist[j], j});
            }
        }
    }
}

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();
        dijkstra(t);
    }
}

signed 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])
        {
            bcnt ++ ;
            dfs(i, bcnt);
        }

    while (mp -- )
    {
        int a, b, c;
        cin >> a >> b >> c;
        din[id[b]] ++ ;
        add(a, b, c);
    }

    topsort();

    for (int i = 1; i <= n; i ++ )
        if (dist[i] > INF / 2) cout << "NO PATH" << endl;
        else cout << dist[i] << endl;

    return 0;
}

代码二:双端队列优化spfa(放个点就行)

#include<iostream>
#include<algorithm>
#include<cmath>
#include<string.h>
#include<string>
#include<vector>
#include<queue>
#include<set>
#include<map>
// #define int long long
#define fi first
#define se second
#define inf 0x3f3f3f3f
using namespace std;
typedef pair<int, int>pii;

const int N =25010,M=2e5;
int h[N],e[M],ne[M],w[M],idx;
int dist[N];
bool st[N];
int n,r,p,s;
void add(int a,int b,int c){
    e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
void spfa(){
    deque<int> q;//pii, Segmentation Fault 
    memset(dist,0x3f,sizeof dist);
    q.push_back(s);
    dist[s]=0;
    while(q.size()){
        auto t=q.front();
        q.pop_front();
        st[t]=false;
        for(int i=h[t];~i;i=ne[i]){
            int j=e[i];
            if(dist[j]>dist[t]+w[i]){
                dist[j]=dist[t]+w[i];
                if(!st[j]){
                    if(dist[j]<dist[q.front()])
                        q.push_front(j);
                    else 
                        q.push_back(j);
                    st[j]=true;
                }
            }
        }
    }
}


signed main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin>>n>>r>>p>>s;
    memset(h,-1,sizeof h);
    while(r--){
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c),add(b,a,c);
    }
    while(p--){
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);
    }
    spfa();
    for(int i=1;i<=n;i++){
        if(dist[i]>inf/2) cout<<"NO PATH"<<endl;
        else cout<<dist[i]<<endl;
    }
    return 0;
}

最优贸易

原题链接

这里是引用

题意:求从点1出发到终点n,在路径中一点买,另一点卖,求赚的钱最多。权值为点权
思路:本题可以转化为枚举每个点为终点,从源点1出发到终点最小的点权,减去从源点n出发到终点最大的点权的最大值。

代码:

#include<iostream>
#include<algorithm>
#include<cmath>
#include<string.h>
#include<string>
#include<vector>
#include<queue>
#include<set>
#include<map>
#define int long long//用手写的循环队列不容易爆栈,不然如果用stl容器最好不要超过1e5,long long最多存储8*1e6
#define fi first
#define se second
#define inf 0x3f3f3f3f
using namespace std;
typedef pair<int, int>pii;

const int N =100010,M=2000010 ;
int n,m;
int w[N];
int hs[N],ht[N],e[M],ne[M],idx;
int dmin[N],dmax[N];
int q[N];
bool st[N];
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){
        auto t=q[hh++];
        if(hh==N) hh=0;
        st[t]=false;
        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]=true;
                }
            }
        }
    }
}
signed main()
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>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;

    return 0;
}

单源最短路的扩展应用

选择最佳路线

原题链接

这里是引用

题意:对于n个点,m条边的有向图,求多个源点到终点s的最短距离。
思路:可以建立一个超级源点,超级源点到题目源点的权值设为0;

代码:

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<string>
#include<vector>
#include<queue>
#include<deque>
#include<set>
#include<map>
#define inf 0x3f3f3f3f3f3f3f3f
#define x first
#define y second
#define int long long 
using namespace std;
const int N=1010,M=40000;
int dist[N];
int st[N];
int g[N][N];
int n,m,s;

int dj(){
    memset(dist,0x3f,sizeof dist);
    memset(st,0,sizeof st);
    dist[0]=0;
    for(int k=0;k<=n;k++){
        int t=-1;
        for(int i=0;i<=n;i++)
            if(!st[i]&&(t==-1||dist[i]<dist[t]))
                t=i;
        st[t]=true;
        for(int i=0;i<=n;i++)
            dist[i]=min(dist[i],dist[t]+g[t][i]);
    } 
    return dist[s];
}

signed main(){
    while(cin>>n>>m>>s){
        memset(g,0x3f,sizeof g);
        for(int i=0;i<=n;i++) g[i][i]=0;
        while(m--){
            int p,q,t;
            cin>>p>>q>>t;
            g[p][q]=min(g[p][q],t);
        }
        int t,o;
        cin>>t;
        while(t--){
            cin>>o;
            g[0][o]=0;
        }
        t=dj();
        if(t==inf) cout<<-1<<endl;
        else cout<<dist[s]<<endl;
    }
    
    return 0;
}

拯救大兵瑞恩

原题链接

这里是引用

题意:求从源点(1,1)到终点(n,m)的最短距离,图上有门和钥匙
思路:dp加最短路

代码:

#include <cstring>
#include <iostream>
#include <algorithm>
#include <deque>
#include <set>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 11, M = 360, P = 1 << 10;

int n, m, k, p;
int h[N * N], e[M], w[M], ne[M], idx;
int g[N][N], key[N * N];
int dist[N * N][P];//dist[x][y]表示到点x状态为y的距离。
bool st[N * N][P];

set<PII> edges;

void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}

void build()
{
    int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
            for (int u = 0; u < 4; u ++ )
            {
                int x = i + dx[u], y = j + dy[u];
                if (!x || x > n || !y || y > m) continue;
                int a = g[i][j], b = g[x][y];
                if (!edges.count({a, b})) add(a, b, 0);
            }
}

int bfs()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1][0] = 0;

    deque<PII> q;
    q.push_back({1, 0});

    while (q.size())
    {
        PII t = q.front();
        q.pop_front();

        if (st[t.x][t.y]) continue;
        st[t.x][t.y] = true;

        if (t.x == n * m) return dist[t.x][t.y];

        if (key[t.x])
        {
            int state = t.y | key[t.x];
            if (dist[t.x][state] > dist[t.x][t.y])
            {
                dist[t.x][state] = dist[t.x][t.y];
                q.push_front({t.x, state});
            }
        }

        for (int i = h[t.x]; ~i; i = ne[i])
        {
            int j = e[i];
            if (w[i] && !(t.y >> w[i] - 1 & 1)) continue;   // 有门并且没有钥匙
            if (dist[j][t.y] > dist[t.x][t.y] + 1)
            {
                dist[j][t.y] = dist[t.x][t.y] + 1;
                q.push_back({j, t.y});
            }
        }
    }

    return -1;
}

int main()
{
    cin >> n >> m >> p >> k;

    for (int i = 1, t = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
            g[i][j] = t ++ ;

    memset(h, -1, sizeof h);
    while (k -- )
    {
        int x1, y1, x2, y2, c;
        cin >> x1 >> y1 >> x2 >> y2 >> c;
        int a = g[x1][y1], b = g[x2][y2];

        edges.insert({a, b}), edges.insert({b, a});
        if (c) add(a, b, c), add(b, a, c);
    }

    build();

    int s;
    cin >> s;
    while (s -- )
    {
        int x, y, c;
        cin >> x >> y >> c;
        key[g[x][y]] |= 1 << c - 1;
    }

    cout << bfs() << endl;

    return 0;
}

最短路计数

原题链接

这里是引用

题意:求从源点1到各点最短路的条数
思路:开一个数量数组cnt;

代码:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#define int long long 
#define inf 0x3f3f3f3f3f3f3f3f
#define pii pair<int,int>
using namespace std;
const int N=1e5+10,M=4e5,mod=100003;
int h[N],e[M],ne[M],w[M],idx;
int dist[N],cnt[N];
bool st[N];
int n,m;
void add(int a,int b,int c){
    e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}

void dj(){
    memset(dist,0x3f,sizeof dist);
    memset(st,0,sizeof st);
    dist[1]=0;
    cnt[1]=1;
    priority_queue<pii,vector<pii>,greater<pii> > heap;
    heap.push({0,1});
    
    
    while(heap.size()){
        auto t=heap.top();
        heap.pop();//不弹出队列会TLE
        int ver=t.second;
        if(st[ver]) continue;
        st[ver]=true;
        
        for(int i=h[ver];~i;i=ne[i]){
            int j=e[i];
            if(dist[j]>dist[ver]+w[i]){
                dist[j]=dist[ver]+w[i];
                cnt[j]=cnt[ver];
                heap.push({dist[j],j});
            }else if(dist[j]==dist[ver]+w[i]){
                cnt[j]=(cnt[j]+cnt[ver])%mod;
            }
        }
    }
}

signed main(){
    cin>>n>>m;
    memset(h,-1,sizeof h);
    while(m--){
        int a,b;
        cin>>a>>b;
        add(a,b,1);
        add(b,a,1);
    }
    
    dj();
    for(int i=1;i<=n;i++)
        cout<<cnt[i]<<endl;
    return 0;
}

观光

原题链接

这里是引用

题意:求最短路和次短路只比最短路多一个单位长度的所有路线数量
思路:dist和cnt开两层分别记录最短路和次短路,dist[j][0]表示到点j的最短路,dist[j][1]表示到点j的次短路
步骤:

  1. 如果当前更新的距离distance+w[i]比最短路小,先更新次短路的dist,cnt,然后压入堆中,再更新最短路的dist,cnt,并且一样压入堆中。
  2. 如果当前更新距离等于最短路,则更新最短路的cnt即可。
  3. 如果当前更新距离小于次短路,则更新次短路的dist,cnt,并且将其压入堆中。
  4. 如果当前更新距离等于次短路,则更新次短路的cnt即可。

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>

using namespace std;

const int N = 1010, M = 20010;

struct Ver
{
    int id, type, dist;
    bool operator> (const Ver &W) const
    {
        return dist > W.dist;
    }
};

int n, m, S, T;
int h[N], e[M], w[M], ne[M], idx;
int dist[N][2], cnt[N][2];
bool st[N][2];

void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}

int dijkstra()
{
    memset(st, 0, sizeof st);
    memset(dist, 0x3f, sizeof dist);
    memset(cnt, 0, sizeof cnt);

    dist[S][0] = 0, cnt[S][0] = 1;
    priority_queue<Ver, vector<Ver>, greater<Ver>> heap;
    heap.push({S, 0, 0});

    while (heap.size())
    {
        Ver t = heap.top();
        heap.pop();

        int ver = t.id, type = t.type, distance = t.dist, count = cnt[ver][type];
        if (st[ver][type]) continue;
        st[ver][type] = true;

        for (int i = h[ver]; ~i; i = ne[i])
        {
            int j = e[i];
            if (dist[j][0] > distance + w[i])
            {
                dist[j][1] = dist[j][0], cnt[j][1] = cnt[j][0];
                heap.push({j, 1, dist[j][1]});
                dist[j][0] = distance + w[i], cnt[j][0] = count;
                heap.push({j, 0, dist[j][0]});
            }
            else if (dist[j][0] == distance + w[i]) cnt[j][0] += count;
            else if (dist[j][1] > distance + w[i])
            {
                dist[j][1] = distance + w[i], cnt[j][1] = count;
                heap.push({j, 1, dist[j][1]});
            }
            else if (dist[j][1] == distance + w[i]) cnt[j][1] += count;
        }
    }

    int res = cnt[T][0];
    if (dist[T][0] + 1 == dist[T][1]) res += cnt[T][1];

    return res;
}

int main()
{
    int cases;
    scanf("%d", &cases);
    while (cases -- )
    {
        scanf("%d%d", &n, &m);
        memset(h, -1, sizeof h);
        idx = 0;

        while (m -- )
        {
            int a, b, c;
            scanf("%d%d%d", &a, &b, &c);
            add(a, b, c);
        }
        scanf("%d%d", &S, &T);

        printf("%d\n", dijkstra());
    }

    return 0;
}

Floyd算法

牛的旅行

原题链接

在这里插入图片描述
题意:求连接两个连通块后的直径最小。
思路:直径最小可能是其中一个连通块的最大直径,也可能是两个连通块最大直径加上连接的边的直径大小。

代码:

#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;
            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)
                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 ++ )
            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;
}

排序

原题链接

这里是引用

题意:求传递闭包,求关系。
思路:不确定时做一遍floyd算法。d=g
怎么排序?
每次找到没有被标记并且没有小于其的字母。

代码:

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 26;

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

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])//自己有大小关系矛盾
            return 2;

    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < i; j ++ )
            if (!d[i][j] && !d[j][i])//i,j关系不确定
                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)
            {
                g[a][b] = 1;
                floyd();
                type = check();
                if (type) t = i;
            }
        }

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

观光之旅

原题链接

这里是引用

题意:求至少包含三个点的最小环路径
思路:Floyd的算法核心,f[k][i][j]表示从i到j中间经过路径点为1~k。
f[k][i][j]表示经过最大点为k,经过i到j的环

这里是引用

代码:

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 110, INF = 0x3f3f3f3f;

int n, m;
int d[N][N], g[N][N];
int pos[N][N];
int path[N], cnt;

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;

    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 d);//没有一条路的时候,到自己不能初始化
    for (int k = 1; k <= n; k ++ )
    {
        for (int i = 1; i < k; i ++ )//当前最大经过点为k-1
            for (int j = i + 1; j < k; j ++ )
                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;
                    get_path(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];
                    pos[i][j] = k;
                }
    }

    if (res == INF) puts("No solution.");
    else
    {
        for (int i = 0; i < cnt; i ++ ) cout << path[i] << ' ';
        cout << endl;
    }

    return 0;
}

牛站

原题链接

这里是引用
在这里插入图片描述

题意:求恰好经过n条边的最短路
思路:d[k][i][j]表示从i到j恰好经过k条边的最短路经。

d[a+b][i][j]=d[a][i][k]+d[b][k][j],k(1~n)
每条边可以相互独立,满足结合律,可以用快速幂,res表示由0,1,2,4,8,…边组成的距离图。
由于点太多了,可以用map来离散化点。
代码:

#include <cstring>
#include <iostream>
#include <algorithm>
#include <map>

using namespace std;

const int N = 210;

int k, n, m, S, E;
int g[N][N];
int res[N][N];

void mul(int c[][N], int a[][N], int b[][N])
{
    static int temp[N][N];
    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);
}

void qmi()
{
    memset(res, 0x3f, sizeof res);
    for (int i = 1; i <= n; i ++ ) res[i][i] = 0;

    while (k)
    {
        if (k & 1) mul(res, res, g);    // res = res * g
        mul(g, g, g);   // g = g * g
        k >>= 1;
    }
}

int main()
{
    cin >> k >> m >> S >> E;

    memset(g, 0x3f, sizeof g);
    map<int, int> ids;
    if (!ids.count(S)) ids[S] = ++ n;
    if (!ids.count(E)) ids[E] = ++ n;
    S = ids[S], E = ids[E];

    while (m -- )
    {
        int a, b, c;
        cin >> c >> a >> b;
        if (!ids.count(a)) ids[a] = ++ n;
        if (!ids.count(b)) ids[b] = ++ n;
        a = ids[a], b = ids[b];

        g[a][b] = g[b][a] = min(g[a][b], c);
    }

    qmi();

    cout << res[S][E] << endl;

    return 0;
}

最小生成树

最短路网络

原题链接

这里是引用

题意:使得各点联通且边权值最小
思路:最小生成树的模板题。用prim和kruskal都行

代码:

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 110;

int n;
int w[N][N];
int dist[N];
bool st[N];

int prim()
{
    int res = 0;
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;

    for (int i = 0; i < n; i ++ )
    {
        int t = -1;
        for (int j = 1; j <= n; j ++ )
            if (!st[j] && (t == -1 || dist[t] > dist[j]))
                t = j;

        res += dist[t];
        st[t] = true;

        for (int j = 1; j <= n; j ++ ) dist[j] = min(dist[j], w[t][j]);
    }

    return res;
}

int main()
{
    cin >> n;
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= n; j ++ )
            cin >> w[i][j];

    cout << prim() << endl;

    return 0;
}

局域网

原题链接

这里是引用

题意:求总路线权值减去最小生成树的权值,即求无用边最大权值
思路:普通的最小生成树

代码:

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 110, M = 210;

int n, m;
struct Edge
{
    int a, b, w;
    bool operator< (const Edge &t)const
    {
        return w < t.w;
    }
}e[M];
int p[N];

int find(int x)
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ ) p[i] = i;

    for (int i = 0; i < m; i ++ )
    {
        int a, b, w;
        cin >> a >> b >> w;
        e[i] = {a, b, w};
    }

    sort(e, e + m);

    int res = 0;
    for (int i = 0; i < m; i ++ )
    {
        int a = find(e[i].a), b = find(e[i].b), w = e[i].w;
        if (a != b) p[a] = b;
        else res += w;//已经联通可以把这条边去掉,也就是无用边
    }

    cout << res << endl;

    return 0;
}

繁忙的都市

原题链接

这里是引用

题意:求最小生成树的一段权值最大的数
思路:用kruskal算法就是形成最小生成树的最后一条边的大小

代码:

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 310, M = 10010;

int n, m;
struct Edge
{
    int a, b, w;
    bool operator< (const Edge &t) const
    {
        return w < t.w;
    }
}e[M];
int p[N];

int find(int x)
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ ) p[i] = i;
    for (int i = 0; i < m; i ++ )
    {
        int a, b, w;
        cin >> a >> b >> w;
        e[i] = {a, b, w};
    }
    sort(e, e + m);

    int res = 0;
    for (int i = 0; i < m; i ++ )
    {
        int a = find(e[i].a), b = find(e[i].b), w = e[i].w;
        if (a != b)
        {
            p[a] = b;
            res = w;
        }
    }

    cout << n - 1 << ' ' << res << endl;

    return 0;
}

联络员

原题链接

这里是引用

题意:在已有一些边的条件下,求最小生成树的权值
思路:已有边将其联通,然后用kruskal算法就最小生成树权。

代码:

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 2010, M = 10010;

int n, m;
struct Edge
{
    int a, b, w;
    bool operator< (const Edge &t) const
    {
        return w < t.w;
    }
}e[M];
int p[N];

int find(int x)
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ ) p[i] = i;

    int res = 0, k = 0;
    for (int i = 0; i < m; i ++ )
    {
        int t, a, b, w;
        cin >> t >> a >> b >> w;
        if (t == 1)
        {
            res += w;
            p[find(a)] = find(b);
        }
        else e[k ++ ] = {a, b, w};
    }

    sort(e, e + k);

    for (int i = 0; i < k; i ++ )
    {
        int a = find(e[i].a), b = find(e[i].b), w = e[i].w;
        if (a != b)
        {
            p[a] = b;
            res += w;
        }
    }

    cout << res << endl;

    return 0;
}

连接格点

原题链接

这里是引用

题意:求二维网格每个点的最小生成树的权值
思路:将二维网格的点转化为一维点,然后进行最小生成树算法,先添加纵向边,然后添加横向边。

代码:

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1010, M = N * N, K = 2 * N * N;

int n, m, k;
int ids[N][N];
struct Edge
{
    int a, b, w;
}e[K];
int p[M];

int find(int x)
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

void get_edges()
{
    int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1}, dw[4] = {1, 2, 1, 2};

    for (int z = 0; z < 2; z ++ )
        for (int i = 1; i <= n; i ++ )
            for (int j = 1; j <= m; j ++ )
                for (int u = 0; u < 4; u ++ )
                    if (u % 2 == z)
                    {
                        int x = i + dx[u], y = j + dy[u], w = dw[u];
                        if (x && x <= n && y && y <= m)
                        {
                            int a = ids[i][j], b = ids[x][y];
                            if (a < b) e[k ++ ] = {a, b, w};
                        }
                    }
}

int main()
{
    cin >> n >> m;

    for (int i = 1, t = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++, t ++ )
            ids[i][j] = t;

    for (int i = 1; i <= n * m; i ++ ) p[i] = i;

    int x1, y1, x2, y2;
    while (cin >> x1 >> y1 >> x2 >> y2)
    {
        int a = ids[x1][y1], b = ids[x2][y2];
        p[find(a)] = find(b);
    }

    get_edges();

    int res = 0;
    for (int i = 0; i < k; i ++ )
    {
        int a = find(e[i].a), b = find(e[i].b), w = e[i].w;
        if (a != b)
        {
            p[a] = b;
            res += w;
        }
    }

    cout << res << endl;

    return 0;
}

最小生成树的扩展应用

新的开始

原题链接

这里是引用

题意:每个点都有权值,然后每个点之间边也有权值,求将所有点联通所需的最小费用
思路:给每个点建立一个超级源点,然后求包括超级源点在内的最小生成树的权值。

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 310;

int n;
int w[N][N];
int dist[N];
bool st[N];

int prim()
{
    memset(dist, 0x3f, sizeof dist);
    dist[0] = 0;

    int res = 0;
    for (int i = 0; i < n + 1; i ++ )
    {
        int t = -1;
        for (int j = 0; j <= n; j ++ )
            if (!st[j] && (t == -1 || dist[t] > dist[j]))
                t = j;
        st[t] = true;
        res += dist[t];

        for (int j = 0; j <= n; j ++ ) dist[j] = min(dist[j], w[t][j]);
    }

    return res;
}

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i ++ )
    {
        scanf("%d", &w[0][i]);
        w[i][0] = w[0][i];
    }

    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= n; j ++ )
            scanf("%d", &w[i][j]);

    printf("%d\n", prim());

    return 0;
}

北极通讯网络

原题链接

这里是引用

题意:有k个卫星可以将卫星的村子直接连接起来联通,然后求最小的d值,边权小于等于d的边可以使用,使得所有点相互联通
思路:最小化二分答案加最小生成树。

代码:

#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 510, M = N * N / 2;

int n, k, m;
struct Edge
{
    int a, b;
    double w;
    bool operator< (const Edge &t) const
    {
        return w < t.w;
    }
}e[M];
PII q[M];
int p[N];

double get_dist(PII a, PII b)
{
    int dx = a.x - b.x;
    int dy = a.y - b.y;
    return sqrt(dx * dx + dy * dy);
}

int find(int x)
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int main()
{
    cin >> n >> k;
    for (int i = 0; i < n; i ++ ) cin >> q[i].x >> q[i].y;
    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < i; j ++ )
            e[m ++ ] = {i, j, get_dist(q[i], q[j])};

    sort(e, e + m);
    for (int i = 0; i < n; i ++ ) p[i] = i;

    int cnt = n;//如果有四个点,两个卫星的话,就只需要联通两条边即可。
    double res = 0;
    for (int i = 0; i < m; i ++ )
    {
        if (cnt <= k) break;

        int a = find(e[i].a), b = find(e[i].b);
        double w = e[i].w;
        if (a != b)
        {
            p[a] = b;
            cnt -- ;
            res = w;
        }
    }

    printf("%.2lf\n", res);

    return 0;
}

走廊泼水节

原题链接

这里是引用

题意:求将一颗最小生成树变成一个完全图的权值最小。
思路:(将两个联通部分的所有点相乘-1)*(w-1)

代码:

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 6010;

int n;
struct Edge
{
    int a, b, w;
    bool operator< (const Edge &t) const
    {
        return w < t.w;
    }
}e[N];
int p[N], size[N];

int find(int x)
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int main()
{
    int T;
    cin >> T;
    while (T -- )
    {
        cin >> n;
        for (int i = 0; i < n - 1; i ++ )
        {
            int a, b, w;
            cin >> a >> b >> w;
            e[i] = {a, b, w};
        }

        sort(e, e + n - 1);
        for (int i = 1; i <= n; i ++ ) p[i] = i, size[i] = 1;

        int res = 0;
        for (int i = 0; i < n - 1; i ++ )
        {
            int a = find(e[i].a), b = find(e[i].b), w = e[i].w;
            if (a != b)
            {
                res += (size[a] * size[b] - 1) * (w + 1);
                size[b] += size[a];
                p[a] = b;
            }
        }

        cout << res << endl;
    }

    return 0;
}

秘密的牛奶运输

原题链接

这里是引用

题意:求次小生成树的权值
思路:

这里是引用
在这里插入图片描述
在这里插入图片描述

b站老师那里听懂的,用最近公共祖先倍增的方法。
链接

代码1:

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 510,M = 1e4 + 10;
struct node{ // 结构体,记录每一个点
    int a,b,w;
    bool f = false; // 标记是否为树边(true为树边,false为非树边)
}edge[M];
int n,m;
int p[N]; // 并查集,父节点
int dist1[N][N],dist2[N][N]; // 分别为最大距离和严格次大距离
int h[N],e[N * 2],w[N * 2],ne[N * 2],idx; // 邻接表存最小生成树
void add(int a,int b,int c){ // 建边
    e[idx] = b,w[idx] = c,ne[idx] = h[a],h[a] = idx ++;
}
bool cmp(node a,node b){ // sort
    return a.w < b.w;
}
int find(int x){ // 并查集找祖宗节点
    if(p[x] != x) return p[x] = find(p[x]);
    return p[x];
}
void dfs(int u,int fa,int maxd1,int maxd2,int d1[],int d2[]){ // dfs预处理最大距离和严格次大距离
    d1[u] = maxd1;
    d2[u] = maxd2; // 记录下最大距离和严格次大距离
    for(int i = h[u];~i;i = ne[i]){ // 遍历与u节点连接的所有边
        int j = e[i]; // 当前点的编号
        if(j != fa){ // 防止遍历到遍历过的点
            int td1 = maxd1,td2 = maxd2; // 找这条边的最大距离和严格次大距离
            if(w[i] > td1) td2 = td1,td1 = w[i]; // 如果这条边权比原来的最大距离更大,那么更新最大距离和严格次大距离
            else if(w[i] < td1 && w[i] > td2) td2 = w[i]; // 如果这条边权比原来的最大距离小,但比严格次大距离大,那么更新严格次大距离
            dfs(j,u,td1,td2,d1,d2); // 继续往下搜
        }
    }
}
int main(){
    memset(h,-1,sizeof h); // 邻接表初始化
    scanf("%d%d",&n,&m); // 读入点数和边数
    for(int i = 1;i <= m;i ++) scanf("%d%d%d",&edge[i].a,&edge[i].b,&edge[i].w); // 读入每条边
    for(int i = 1;i <= n;i ++) p[i] = i; // 并查集初始化
    sort(edge + 1,edge + m + 1,cmp); // 把边按照从小到大排序
    long long sum = 0; // 最小生成树
    for(int i = 1;i <= m;i ++){ // 求最小生成树
        int a = edge[i].a,b = edge[i].b,w = edge[i].w;
        int fa = find(a),fb = find(b);
        if(fa != fb){
            p[fa] = fb;
            sum += w;
            edge[i].f = true; // 标记为树边
            add(a,b,w);
            add(b,a,w); // 用邻接表建树
        }
    }
    for(int i = 1;i <= n;i ++) dfs(i,-1,-1e9,-1e9,dist1[i],dist2[i]); // 预处理最大距离和严格次大距离,点i到每个点之间一段路线的最大距离和次大距离
    long long res = 1e18; // 严格次小距离
    for(int i = 1;i <= m;i ++){ // 枚举每条边
        if(!edge[i].f){ // 确定是非树边
            long long t;
            int a = edge[i].a,b = edge[i].b,w = edge[i].w;
            if(w > dist1[a][b]) t = sum + w - dist1[a][b]; // 如果这条边比原来的最大距离大,那么尝试替换这条边
            else if(w > dist2[a][b]) t = sum + w - dist2[a][b];  // 如果这条边比原来的严格次大距离大,那么尝试替换这条边
            res = min(res,t); // 取最小的t
        }
    }
    printf("%lld\n",res); // 输出答案
    return 0;
}

代码2:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值