AcWing算法提高课【第三章图论】第一部分

单源最短路的建图方式

热浪

题目:

德克萨斯纯朴的民众们这个夏天正在遭受巨大的热浪!!!

他们的德克萨斯长角牛吃起来不错,可是它们并不是很擅长生产富含奶油的乳制品。

农夫John此时身先士卒地承担起向德克萨斯运送大量的营养冰凉的牛奶的重任,以减轻德克萨斯人忍受酷暑的痛苦。

John已经研究过可以把牛奶从威斯康星运送到德克萨斯州的路线。

这些路线包括起始点和终点一共有 T 个城镇,为了方便标号为 1 到 T。

除了起点和终点外的每个城镇都由 双向道路 连向至少两个其它的城镇。

每条道路有一个通过费用(包括油费,过路费等等)。

给定一个地图,包含 C 条直接连接 2 个城镇的道路。

每条道路由道路的起点 Rs,终点 Re 和花费 Ci 组成。

求从起始的城镇 Ts 到终点的城镇 Te 最小的总费用。

输入格式
第一行: 4 个由空格隔开的整数: T,C,Ts,Te;

第 2 到第 C+1 行: 第 i+1 行描述第 i 条道路,包含 3 个由空格隔开的整数: Rs,Re,Ci。

输出格式
一个单独的整数表示从 Ts 到 Te 的最小总费用。

数据保证至少存在一条道路。

数据范围
1≤T≤2500,
1≤C≤6200,
1≤Ts,Te,Rs,Re≤T,
1≤Ci≤1000
输入样例:
7 11 5 4
2 4 2
1 4 3
7 2 2
3 4 3
5 7 5
7 3 3
6 1 1
6 3 4
2 4 3
5 6 3
7 2 1
输出样例:
7

分析:

非常纯正的单源最短路模型,模板题

代码:

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

using namespace std;

typedef pair<int, int> PII;

#define x first
#define y second

const int N = 2510, M = 6210 << 1, inf = 0x3f3f3f3f;

int n, m, S, T; 
int head[N], ver[M], edge[M], Next[M], tot;
bool st[N];
int d[N];

void add(int x, int y, int z)
{
    ver[++ tot] = y, edge[tot] = z, Next[tot] = head[x], head[x] = tot;
}

int dijkstra()
{
    memset(d, 0x3f, sizeof d);
    d[S] = 0;
    priority_queue<PII, vector<PII>, greater<PII>> q;
    q.push({0, S});
    
    while (q.size())
    {
        PII t = q.top();
        q.pop();
        
        int x = t.y;
        if (st[x]) continue;
        st[x] = true;
        
        for (int i = head[x]; i; i = Next[i])
        {
            int y = ver[i], z = edge[i];
            if (d[y] > d[x] + z)
            {
                d[y] = d[x] + z;
                q.push({d[y], y});
                
            }
            
            // if (d[T] < inf) return d[T];
        }
    }
    return d[T];
}
int main()
{
    scanf("%d%d%d%d", &n, &m, &S, &T);
    while (m -- )
    {
        int x, y, z; scanf("%d%d%d", &x, &y, &z);
        add(x, y, z); add(y, x, z);
    }
    
    printf("%d\n", dijkstra());
    
    return 0;
} 

题目:1128. 信使

战争时期,前线有 n 个哨所,每个哨所可能会与其他若干个哨所之间有通信联系。

信使负责在哨所之间传递信息,当然,这是要花费一定时间的(以天为单位)。

指挥部设在第一个哨所。

当指挥部下达一个命令后,指挥部就派出若干个信使向与指挥部相连的哨所送信。

当一个哨所接到信后,这个哨所内的信使们也以同样的方式向其他哨所送信。信在一个哨所内停留的时间可以忽略不计。

直至所有 n 个哨所全部接到命令后,送信才算成功。

因为准备充足,每个哨所内都安排了足够的信使(如果一个哨所与其他 k 个哨所有通信联系的话,这个哨所内至少会配备 k 个信使)。

现在总指挥请你编一个程序,计算出完成整个送信过程最短需要多少时间。

输入格式
第 1 行有两个整数 n 和 m,中间用 1 个空格隔开,分别表示有 n 个哨所和 m 条通信线路。

第 2 至 m+1 行:每行三个整数 i、j、k,中间用 1 个空格隔开,表示第 i 个和第 j 个哨所之间存在 双向 通信线路,且这条线路要花费 k 天。

输出格式
一个整数,表示完成整个送信过程的最短时间。

如果不是所有的哨所都能收到信,就输出-1。

数据范围
1≤n≤100,
1≤m≤200,
1≤k≤1000
输入样例:
4 4
1 2 4
2 3 7
2 4 1
3 4 6
输出样例:
11 

  

分析:

由题目可以看出,无所谓起点和终点,因为从x走到y,意味着y也会走到x,直接Floyd算法,求最短路径的最大值就好了

代码:

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

using namespace std;

const int N = 110, inf = 1e9;

int n, m;
int d[N][N];

void print()
{
    for (int i = 1; i <= n; i ++ ) 
    {
        for (int j = 1; j <= n; j ++ ) 
            printf("%20d ", d[i][j]);
        puts("");
    }
}
int main()
{
    scanf("%d%d", &n, &m);
    
    for (int i = 1; i <= n; i ++ )  
        for (int j = 1; j <= n; j ++ ) 
            if(i == j) d[i][j] = 0;
            else d[i][j] = inf;
    // print();
    while (m -- ) 
    {
        int x, y, z; scanf("%d%d%d", &x, &y, &z);
        d[x][y] = d[y][x] = min(d[x][y], z);
    }
    // print();
    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]);
    // print();
    int ans = 0;
    for (int i = 1; i <= n; i ++ ) 
        if (d[1][i] == inf) 
        {
            puts("-1");
            return 0;
        }
        else ans = max(ans, d[1][i]);
    
    printf("%d\n", ans);
    
    return 0;
}

  

1127. 香甜的黄油 

题目:

农夫John发现了做出全威斯康辛州最甜的黄油的方法:糖。

把糖放在一片牧场上,他知道 N 只奶牛会过来舔它,这样就能做出能卖好价钱的超甜黄油。

当然,他将付出额外的费用在奶牛上。

农夫John很狡猾,就像以前的巴甫洛夫,他知道他可以训练这些奶牛,让它们在听到铃声时去一个特定的牧场。

他打算将糖放在那里然后下午发出铃声,以至他可以在晚上挤奶。

农夫John知道每只奶牛都在各自喜欢的牧场(一个牧场不一定只有一头牛)。

给出各头牛在的牧场和牧场间的路线,找出使所有牛到达的路程和最短的牧场(他将把糖放在那)。

数据保证至少存在一个牧场和所有牛所在的牧场连通。

输入格式
第一行: 三个数:奶牛数 N,牧场数 P,牧场间道路数 C。

第二行到第 N+1 行: 1 到 N 头奶牛所在的牧场号。

第 N+2 行到第 N+C+1 行:每行有三个数:相连的牧场A、B,两牧场间距 D,当然,连接是双向的。

输出格式
共一行,输出奶牛必须行走的最小的距离和。

数据范围
1≤N≤500,
2≤P≤800,
1≤C≤1450,
1≤D≤255
输入样例:
3 4 5
2
3
4
1 2 1
1 3 5
2 3 7
2 4 3
3 4 5
输出样例:
8

分析:

找到一个牧场,让其他奶牛到这个牧场集合的最短距离之和,这可不是最小生成树,每个奶牛走的路走只能算是它自己的。

我们可以将每个牧场都求一边最短路,然后保留最小的。对优化版的dijkstra算法时间复杂度为O(nlogn),加上遍历每个牧场,总的时间复杂度为800 * 800 * log800 = 6 400 000时间复杂度还好。

代码:

#include <cstdio>
#include <algorithm>
#include <queue>

using namespace std;

typedef pair<int, int> PII;

#define x first
#define y second

const int N = 810, M = 3010, inf = 1e9;

int n, p, c;
int head[N], ver[M], edge[M], Next[M], tot;
int id[N];

void add(int x, int y, int z)
{
    ver[++ tot] = y, edge[tot] = z, Next[tot] = head[x], head[x] = tot;
}

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

int dijkstra(int u)
{
    for (int i = 0; i <= p; i ++ )  dist[i] = inf;
    for (int i = 0; i <= p; i ++ ) st[i] = false;
    dist[u] = 0;
    priority_queue<PII, vector<PII>, greater<PII>> q;
    q.push({0, u});
    
    while (q.size())
    {
        PII t = q.top();
        q.pop();
        
        int x = t.y;
        if (st[x]) continue;
        st[x] = true;
        
        for (int i = head[x]; i; i = Next[i])
        {
            int y = ver[i], z = edge[i];
            if (dist[y] > dist[x] + z)
            {
                dist[y] = dist[x] + z;
                q.push({dist[y], y});
            }
        }
    }
    
    int ans = 0;
    for (int i = 1; i <= n; i ++ ) 
        ans += dist[id[i]];
    if (ans <= 0) return inf;//要是有那种不可连通的多的话,就会爆int,是一个负值,就返回的大值
    return ans;//其他的话就不用管了,反正下面还有min函数,只会保留最小值
}

int main()
{
    scanf("%d%d%d", &n, &p, &c);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &id[i]);
    
    while (c -- )
    {
        int x, y, z; scanf("%d%d%d", &x, &y, &z);
        add(x, y, z); add(y, x, z);
    }
    
    //尝试在每一个牧场中放置糖
    int ans = inf;
    for (int i = 1; i <= p; i ++ ) 
        ans = min(ans, dijkstra(i));
    
    printf("%d\n", ans);
    
    return 0;
}

1126. 最小花费

题目:

在 n 个人中,某些人的银行账号之间可以互相转账。

这些人之间转账的手续费各不相同。

给定这些人之间转账时需要从转账金额里扣除百分之几的手续费,请问 A 最少需要多少钱使得转账后 B 收到 100 元。

输入格式
第一行输入两个正整数 n,m,分别表示总人数和可以互相转账的人的对数。

以下 m 行每行输入三个正整数 x,y,z,表示标号为 x 的人和标号为 y 的人之间互相转账需要扣除 z% 的手续费 ( z<100 )。

最后一行输入两个正整数 A,B。

数据保证 A 与 B 之间可以直接或间接地转账。

输出格式
输出 A 使得 B 到账 100 元最少需要的总费用。

精确到小数点后 8 位。

数据范围
1≤n≤2000,
m≤105
输入样例:
3 3
1 2 1
2 3 2
1 3 3
1 3
输出样例:
103.07153164

 分析 

原本还想着,不处理z,然后求一下乘积最小,但是发现不会哦,还是要减去后的,概率相乘。 

 代码:

代码1:转化为要多少钱才能转化成一个单位的钱

#include <cstdio>
#include <queue>

using namespace std;

const int N = 2010, M = 200010, inf = 1e9;

typedef pair<double, int> PDI;

#define x first
#define y second

int n, m, S, T; 
int head[N], ver[M], Next[M], tot;
double edge[M];
double dist[N];
bool st[N];

void add(int x, int y, int z)
{
    ver[++ tot] = y, edge[tot] = 100.0 / (100.0 - z),
    Next[tot] = head[x], head[x] = tot;
}

void dijkstra()
{
    for (int i = 0; i <= n; i ++ ) dist[i] = inf;
    dist[S] = 1;
    priority_queue<PDI, vector<PDI>, greater<PDI>> q;
    q.push({1, S});
    
    while (q.size())
    {
        PDI t = q.top();
        q.pop();
        
        int x = t.y;
        
        if (st[x]) continue;
        st[x] = true;
        
        for (int i = head[x]; i; i = Next[i])
        {
            int y = ver[i];
            double z = edge[i];
            if (dist[y] > dist[x] * z)
            {
                dist[y] = dist[x] * z;
                q.push({dist[y], y});
            }
        }
    }
}

int main()
{
    scanf("%d%d", &n, &m);
    
    while (m -- ) 
    {
        int x, y, z; scanf("%d%d%d", &x, &y, &z);
        add(x, y, z), add(y, x, z);
    }
    
    scanf("%d%d", &S, &T);
    dijkstra();
    
    printf("%.8lf\n", dist[T] * 100);
    
    return 0;
}

 代码2:按照题目给的转换率,p = 1.0 - 0.01 * c ,然后求一条最大路。

#include <cstdio>
#include <queue>
#include <algorithm>

using namespace std;

typedef pair<double, int> PDI;

#define x first
#define y second

const int N = 2010, M = 200010, inf = 1e9;

int n, m, S, T; 
int head[N], ver[M], Next[M], tot;
double edge[M];

void add(int x, int y, int z)
{
    ver[++ tot] = y, edge[tot] = (double)(1.0 - 0.01 * z);
    Next[tot] = head[x], head[x] = tot;
}

double dist[N];
bool st[N];

void dijkstra()
{
    // for (int i = 1; i <= n; i ++ ) dist[i] = inf;
    dist[S] = 1.0;
    priority_queue<PDI> q;
    // priority_queue<PDI, vector<PDI>, greater<PDI>> q;
    q.push({1.0, S});
    
    while (q.size())
    {
        PDI t = q.top();
        q.pop();
        
        int x = t.y;
        
        if (st[x]) continue;
        st[x] = true;
        
        for (int i = head[x]; i; i = Next[i])
        {
            int y = ver[i];
            double z = edge[i];
            
            if (dist[y] < dist[x] * z)
            {
                dist[y] = dist[x] * z;
                q.push({dist[y], y});
            }
        }
    }
}

int main()
{
    scanf("%d%d", &n, &m);
    
    while (m -- ) 
    {
        int x, y, z; scanf("%d%d%d", &x, &y, &z);
        add(x, y, z), add(y, x, z);
    }
    
    scanf("%d%d", &S, &T);
    
    dijkstra();
    
    printf("%.8lf\n", 100 / dist[T]);
    
    return 0;
}

代码3:这次使用spfa求得,感觉代码更简洁

#include <cstdio>
#include <queue>
#include <algorithm>

using namespace std;

typedef pair<double, int> PDI;

#define x first
#define y second

const int N = 2010, M = 200010, inf = 1e9;

int n, m, S, T; 
int head[N], ver[M], Next[M], tot;
double edge[M];

void add(int x, int y, int z)
{
    ver[++ tot] = y, edge[tot] = (double)(1.0 - 0.01 * z);
    Next[tot] = head[x], head[x] = tot;
}

double dist[N];
bool st[N];

void dijkstra()
{
    dist[S] = 1.0;
    queue<int> q;
    q.push(S);
    st[S] = true;
    
    while (q.size())
    {
        int x = q.front();
        q.pop();
        
        st[x] = false;
        
        for (int i = head[x]; i; i = Next[i])
        {
            int y = ver[i];
            double z = edge[i];
            if (dist[y] < dist[x] * z)
            {
                dist[y] = dist[x] * z;
                if (!st[y])
                {
                    st[y] = true;
                    q.push(y);
                }
            }
        }
    }
    
}

int main()
{
    scanf("%d%d", &n, &m);
    
    while (m -- ) 
    {
        int x, y, z; scanf("%d%d%d", &x, &y, &z);
        add(x, y, z), add(y, x, z);
    }
    
    scanf("%d%d", &S, &T);
    
    dijkstra();
    
    printf("%.8lf\n", 100 / dist[T]);
    
    return 0;
}

 920. 最优乘车 

题目:

H 城是一个旅游胜地,每年都有成千上万的人前来观光。

为方便游客,巴士公司在各个旅游景点及宾馆,饭店等地都设置了巴士站并开通了一些单程巴士线路。

每条单程巴士线路从某个巴士站出发,依次途经若干个巴士站,最终到达终点巴士站。

一名旅客最近到 H 城旅游,他很想去 S 公园游玩,但如果从他所在的饭店没有一路巴士可以直接到达 S 公园,则他可能要先乘某一路巴士坐几站,再下来换乘同一站台的另一路巴士,这样换乘几次后到达 S 公园。

现在用整数 1,2,…N 给 H 城的所有的巴士站编号,约定这名旅客所在饭店的巴士站编号为 1,S 公园巴士站的编号为 N。

写一个程序,帮助这名旅客寻找一个最优乘车方案,使他在从饭店乘车到 S 公园的过程中换乘的次数最少。

输入格式
第一行有两个数字 M 和 N,表示开通了 M 条单程巴士线路,总共有 N 个车站。

从第二行到第 M+1 行依次给出了第 1 条到第 M 条巴士线路的信息,其中第 i+1 行给出的是第 i 条巴士线路的信息,从左至右按运行顺序依次给出了该线路上的所有站号,相邻两个站号之间用一个空格隔开。

输出格式
共一行,如果无法乘巴士从饭店到达 S 公园,则输出 NO,否则输出最少换乘次数,换乘次数为 0 表示不需换车即可到达。

数据范围
1≤M≤100,
1≤N≤500
输入样例:
3 7
6 7
4 7 3 6
2 1 3 5
输出样例:
2 

分析:

处理就在于那两个for循环,表示前面得点,能直接到后面得点,而且距离都是1,这样bfs一遍,就OK了

代码:

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

using namespace std;

const int N = 510, inf = 1e9;

int n, m;
bool g[N][N];
int a[N];
int q[N];
int dist[N];

void bfs()
{
    for (int i = 1; i <= n; i ++ ) dist[i] = inf;
    dist[1] = 0;
    int tt = 0, hh = 0;
    q[0] = 1;
    
    while (hh <= tt)
    {
        int x = q[hh ++ ];
        
        for (int i = 1; i <= n; i ++ ) 
            if (g[x][i] && dist[i] > dist[x] + 1)
            {
                dist[i] = dist[x] + 1;
                q[++ tt] = i;
            }
    }
}
int main()
{
    scanf("%d%d", &m, &n);
    
    getchar();
    while (m -- )
    {
        string line; 
        getline(cin, line);
        stringstream ssin(line);
        int cnt = 0, p;
        while (ssin >> p) a[++ cnt] = p;
        
        for (int i = 1; i <= cnt; i ++ ) //当前这个点,到它后面得点得距离为1
            for (int j = i + 1; j <= cnt; j ++ ) 
                g[a[i]][a[j]] = true;
    }
    
    bfs();
    
    if (dist[n] == inf) puts("NO");
    else printf("%d\n", max(dist[n] - 1, 0));
    
    return 0;
}

  903. 昂贵的聘礼

题目:

年轻的探险家来到了一个印第安部落里。

在那里他和酋长的女儿相爱了,于是便向酋长去求亲。

酋长要他用 10000 个金币作为聘礼才答应把女儿嫁给他。

探险家拿不出这么多金币,便请求酋长降低要求。

酋长说:”嗯,如果你能够替我弄到大祭司的皮袄,我可以只要 8000 金币。如果你能够弄来他的水晶球,那么只要 5000 金币就行了。”

探险家就跑到大祭司那里,向他要求皮袄或水晶球,大祭司要他用金币来换,或者替他弄来其他的东西,他可以降低价格。

探险家于是又跑到其他地方,其他人也提出了类似的要求,或者直接用金币换,或者找到其他东西就可以降低价格。

不过探险家没必要用多样东西去换一样东西,因为不会得到更低的价格。

探险家现在很需要你的帮忙,让他用最少的金币娶到自己的心上人。

另外他要告诉你的是,在这个部落里,等级观念十分森严。

地位差距超过一定限制的两个人之间不会进行任何形式的直接接触,包括交易。

他是一个外来人,所以可以不受这些限制。

但是如果他和某个地位较低的人进行了交易,地位较高的的人不会再和他交易,他们认为这样等于是间接接触,反过来也一样。

因此你需要在考虑所有的情况以后给他提供一个最好的方案。

为了方便起见,我们把所有的物品从 1 开始进行编号,酋长的允诺也看作一个物品,并且编号总是 1。

每个物品都有对应的价格 P,主人的地位等级 L,以及一系列的替代品Ti和该替代品所对应的”优惠” Vi。

如果两人地位等级差距超过了 M,就不能”间接交易”。

你必须根据这些数据来计算出探险家最少需要多少金币才能娶到酋长的女儿。

输入格式
输入第一行是两个整数 M,N,依次表示地位等级差距限制和物品的总数。

接下来按照编号从小到大依次给出了 N 个物品的描述。

每个物品的描述开头是三个非负整数 P、L、X,依次表示该物品的价格、主人的地位等级和替代品总数。

接下来 X 行每行包括两个整数 T 和 V,分别表示替代品的编号和”优惠价格”。

输出格式
输出最少需要的金币数。

数据范围
1≤N≤100,
1≤P≤10000,
1≤L,M≤N,
0≤X<N
输入格式
1 4
10000 3 2
2 8000
3 5000
1000 2 1
4 200
3000 2 1
4 200
50 2 0
输出格式
5250

  分析:

这里的等级差距是,最高和最低的等级差距不能大于m,而不是相邻两个的差距。

而且,这里是不是酋长不是等级最高的,为什么要将[level[1] - m, level[1] + m]中每个(i, i + m)的区间都要搜一遍

这一题,重点在于怎么建图。一开始,我想的是,每个点都有点权,两点之间的单向边有边权,然后枚举起点,不断的用点权加边权更新其他点权,最后得到节点1号点的最小点权。每次判断其他点与起点的level差是否大于m,大于就continue,这没问题啊,为什么不对呢?

建立虚拟原点的代码:

#include <cstdio>
#include <queue>
#include <algorithm>

using namespace std;

const int N = 110, M = 10010, inf = 1e9;

int m, n;
int head[N], ver[M], edge[M], Next[M], tot;
int level[N];

void add(int x, int y, int z)
{
    ver[++ tot] = y, edge[tot] = z, Next[tot] = head[x], head[x] = tot;
}

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

int spfa(int l, int r)
{
    for (int i = 0; i <= n; i ++ ) dist[i] = inf, st[i] = false;
    dist[0] = 0;
    queue<int> q;
    q.push(0);
    st[0] = true;
    
    while (q.size())
    {
        int x = q.front();
        q.pop();
        
        st[x] = false;
        
        for (int i = head[x]; i; i = Next[i])
        {
            int y = ver[i], z = edge[i];
            if (level[y] < l || level[y] > r) continue;    
            if (dist[y] > dist[x] + z)
            {
                dist[y] = dist[x] + z;
                if (!st[y])
                {
                    st[y] = true;
                    q.push(y);
                }
            }
        }
    }
    return dist[1];
}

int main()
{
    scanf("%d%d", &m, &n);
    for (int i = 1; i <= n; i ++ ) 
    {
        int price, cnt;
        scanf("%d%d%d", &price, &level[i], &cnt);
        add(0, i, price);
        while (cnt -- )
        {
            int id, v;
            scanf("%d%d", &id, &v);
            add(id, i, v);
        }
    }
    
    int res = inf;
    for (int i = level[1] - m; i <= level[1]; i ++ ) res = min(res, spfa(i, i + m));
    
    printf("%d\n", res);
    
    return 0;
}

  

1135. 新年好

题目:

重庆城里有 n 个车站,m 条 双向 公路连接其中的某些车站。

每两个车站最多用一条公路连接,从任何一个车站出发都可以经过一条或者多条公路到达其他车站,但不同的路径需要花费的时间可能不同。

在一条路径上花费的时间等于路径上所有公路需要的时间之和。

佳佳的家在车站 1,他有五个亲戚,分别住在车站 a,b,c,d,e。

过年了,他需要从自己的家出发,拜访每个亲戚(顺序任意),给他们送去节日的祝福。

怎样走,才需要最少的时间?

输入格式
第一行:包含两个整数 n,m,分别表示车站数目和公路数目。

第二行:包含五个整数 a,b,c,d,e,分别表示五个亲戚所在车站编号。

以下 m 行,每行三个整数 x,y,t,表示公路连接的两个车站编号和时间。

输出格式
输出仅一行,包含一个整数 T,表示最少的总时间。

数据范围
1≤n≤50000,
1≤m≤105,
1<a,b,c,d,e≤n,
1≤x,y≤n,
1≤t≤100
输入样例:
6 6
2 3 4 5 6
1 2 8
2 3 3
3 4 4
4 5 5
5 6 2
1 6 7
输出样例:
21

  

分析:

题解是这样的,我们先将1号点当起点,算一次最短路,然后接着将5位亲戚当作起点,一次算一次最短路,并将所得最短路存入dist[][]数组中。因为我们每次走最短路但是,比一定是总的最短路,所以,我们将5个节点全排列,枚举每种顺序,得到的最小值就是我们的最短路了。

时间复杂度位O(6nlogn + 6!)

注意:这题卡SPFA

 代码:

#include <cstdio>
#include <queue>
#include <algorithm>

using namespace std;

const int N = 50010, M = 200010, inf = 1e9;

typedef pair<int, int> PII;

#define x first
#define y second

typedef long long ll;

int n, m;
int head[N], ver[M], edge[M], Next[M], tot;
int a[6];
ll dist[6][N];
bool st[N];

void add(int x, int y, int z)
{
    ver[++ tot] = y, edge[tot] = z, Next[tot] = head[x], head[x] = tot;
}
//这道题卡的就是spfa算法
// void spfa(int S, ll dist[])
// {
//     for (int i = 0; i <= n; i ++ ) dist[i] = inf;
//     for (int i = 0; i <= n; i ++ ) st[i] = false;
//     st[S] = true;
//     dist[S] = 0;
//     queue<int> q;
//     q.push(S);
    
//     while (q.size())
//     {
//         int x = q.front();
//         q.pop();
        
//         st[x] = false;
        
//         for (int i = head[x]; i; i = Next[i])
//         {
//             int y = ver[i], z = edge[i];
            
//             if (dist[y] > dist[x] + z)
//             {
//                 dist[y] = dist[x] + z;
//                 if (!st[y])
//                 {
//                     st[y] = true;
//                     q.push(y);
//                 }
//             }
//         }
//     }
    
// }

void dijkstra(int S, ll dist[])
{
    for (int i = 1; i <= n; i ++ ) dist[i] = inf, st[i] = false;
    dist[S] = 0;
    priority_queue<PII, vector<PII>, greater<PII>> q;
    q.push({0, S});
    
    while (q.size())
    {
        PII t = q.top();
        q.pop();
        
        int x = t.y;
        if (st[x]) continue;
        st[x] = true;
        
        for (int i = head[x]; i; i = Next[i])
        {
            int y = ver[i], z = edge[i];
            if (dist[y] > dist[x] + z)
            {
                dist[y] = dist[x] + z;
                q.push({dist[y], y});
            }
        }
    }
}
int path[20];
bool vis[6];
ll ans = inf;

int id[N];
//两种dfs方式都是对的
// ll dfs(int u, int start, ll distance)
// {
//     if (u == 6) return distance;
//     ll res = inf;
//     for (int i = 1; i <= 5; i ++ )
//         if (!vis[i])
//         {
//             vis[i] = true;
//             res = min(res, dfs(u + 1, i, distance + dist[start][a[i]]));
//             vis[i] = false;
//         }
//     return res;
// }

void dfs(int u)
{
    if (u == 6)
    {
        // for (int i = 0; i <= 5; i ++ ) printf("%d ", path[i]);
        // puts("");
        // for (int i = 0; i <= 5; i ++ ) printf("%d ", a[path[i]]);
        // puts("");
        // for (int i = 0; i <= 5; i ++ ) printf("%d ", id[path[i]]);
        // puts("");
        ll res = 0;
        // for (int i = 1; i <= 5; i ++ ) 
            // res += dist[id[a[path[i - 1]]]][a[path[i]]];
        
        for (int i = 1; i <= 5; i ++ )
            res += dist[id[path[i - 1]]][path[i]];
        ans = min(ans, res);
        return;
    }
    
    for (int i = 1; i <= 5; i ++ )
        if (!vis[i])
        {
            vis[i] = true;
            // path[u] = i;
            path[u] = a[i];
            dfs(u + 1);
            vis[i] = false;
        }
}

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= 5; i ++ ) scanf("%d", &a[i]);
    
    while (m -- )
    {
        int x, y, z; scanf("%d%d%d", &x, &y, &z);
        add(x, y, z); add(y, x, z);
    }
    
    a[0] = 1;
    // for (int i = 0; i < 6; i ++ )
        // spfa(a[i], dist[i]);
    
    for (int i = 0; i < 6; i ++ ) 
        dijkstra(a[i], dist[i]);
    
    for (int i = 0; i <= 5; i ++ )
        id[a[i]] = i;
    
    // for (int i = 0; i <= 5; i ++ ) 
    // {
    //     for (int j = 0; j <= n; j ++ ) 
    //         printf("%d ", dist[i][j]);
    //     puts("");
    // }
    
    dfs(1);
    printf("%lld\n", ans);
    // printf("%lld\n", dfs(1, 0, 0));
    
    
    return 0;
}

340. 通信线路

题目:

在郊区有 N 座通信基站,P 条 双向 电缆,第 i 条电缆连接基站 Ai 和 Bi。

特别地,1 号基站是通信公司的总站,N 号基站位于一座农场中。

现在,农场主希望对通信线路进行升级,其中升级第 i 条电缆需要花费 Li。

电话公司正在举行优惠活动。

农产主可以指定一条从 1 号基站到 N 号基站的路径,并指定路径上不超过 K 条电缆,由电话公司免费提供升级服务。

农场主只需要支付在该路径上剩余的电缆中,升级价格最贵的那条电缆的花费即可。

求至少用多少钱可以完成升级。

输入格式
第 1 行:三个整数 N,P,K。

第 2..P+1 行:第 i+1 行包含三个整数 Ai,Bi,Li。

输出格式
包含一个整数表示最少花费。

若 1 号基站与 N 号基站之间不存在路径,则输出 −1。

数据范围
0≤K<N≤1000,
1≤P≤10000,
1≤Li≤1000000
输入样例:
5 7 1
1 2 5
3 1 4
2 4 8
3 2 3
5 2 9
3 4 7
4 5 6
输出样例:
4

 分析:

 

前言:好吧,我看到的这题可以用分层图最短路,二分+最短路,DP+最短路,双端队列BFS+二分,等着,都给你管上~~~
我看到这个题的第一想法是二分,这是最大值最小化问题?将边权排个序,然后二分边权。但是后来我发现,情况不是这样的,因为不知道怎么去确定比二分的这个边权大的边在通路中超没超过k条。唉,可以试一下的嘛,如果大于边权,就消耗一次免费的机会,check就是判k。

如果我们但但看这个题,让我们求的并不是全局的最短路径,我们要求的是存在的一条路径,而且这条路径与其他路径相比,它的第k+1大边权是最小的。当然,要是存在一条路径的边的个数不超过k就更好了,就没有花费了。

  

 

posted @ 2021-05-02 15:59  rookie161  阅读(171)  评论(0编辑  收藏  举报