【Educational Codeforces Round 102】 E. Minimum Path(分层图最短路)

参考博客:
做法1
做法2
分层图最短路讲解:链接

题意

给出一个 n n n个点 m m m条边的带权无向联通图。定义一个经过 k k k条边的路径的权重为
∑ i = 1 k w e i − max ⁡ i = 1 k w e i + min ⁡ i = 1 k w e i \sum_{i=1}^{k}w_{ei}−\max_{i=1}^{k}w_{ei}+\min_{i=1}^{k}w_{ei} i=1kweimaxi=1kwei+mini=1kwei e 1 , e 2 , e 3 , . . . , e k e_{1},e_{2},e_{3},...,e_{k} e1,e2,e3,...,ek k k k条边的编号。
要求的是从 1 1 1顶点到 i i i顶点所有路径中权重最小的路径的权重。 ( 2 ≤ i ≤ n ) (2 \leq i \leq n) (2in)

做法

分层图最短路,两种做法分别是分层图最短路的两种写法。
可以将公式中的减去最大值看成最大的边代价为 0 0 0,将加上最小边看成最小边的代价变成原来的 2 2 2倍。
现在在这个图上加上两个规则。对于一条路径,可以选择一条边让这条边的代价为 0 0 0,选择一条边让这条边的代价变为原来的 2 2 2倍。对于顶点 i i i来说,答案就是在路径中应用这两条规则之后, 1 1 1 i i i的最短路。
这里会有疑问,为什么这样选出来的最短路符合题中路径权重的定义,也就是说这样选出来的最短路,它们的操作为什么一定是对最大值和最小值。
在一条路径上肯定是将上述两条规则用分别用在最大的边上和最小的边上的到的路径权重之和最小。如果这个最短路不是将两个规则作用于最大和最小边,那么作用于最大和最小边的路径权重一定比这个最短路小(从路径之和中减去的值变小,增加的值变大),这与它是最短路就相矛盾了。
所以问题简化为,从 1 1 1到达每一个点的路径中要选择一条边让其为0,再选择一条边让其为原来的两倍,问在这种情况下的最短路。

做法一:

将原来的图扩展成3层。
选择一个边为0的做法就是让两层之间原本有边的点连上权值为0的边。在第一层的点是没有经过这个操作的点,当选择通向2层的边后,就到达了第二层的一个点,表示已经经过了让一个边为0的操作。
选择一个边为原来的两倍的做法就是让两层之间原本有边的点之间连上权值为原本两倍的点,从第二层的 u u u到达第三层的 v v v就是经过了原本边两倍的边到达了 v v v,第三层的点也就表示已经经过了两倍的操作的点。
当然各层之间的点还是需要保留原本的边,表示在本层移动。答案要求经过两个操作之后的最短路,那么就输出第三层各点的最短路就行。
建立一个图,第一层连向第二层的点的边权值为0,第三层连向第二层的点的边权为原来的2倍。
发现这样0的边总在2倍边之前,所以还要建一个一层到二层是二倍,二层到三层是0的图。这样之前之后就全有了。那么又发现,如果两个操作选择的边是同一个边怎么办呢,上面两个图都不能处理。其实如果两个选择是同一条边的话,减去这条边和加上这条边就是抵消了,那路径就是原本的路径。那么这只要最后和第一层图里的点的最短路取一个最小值就行了。

代码:

/*
 * @file E.cpp
 * @path D:\code\ACM\codeforces\EDU_102\E.cpp
 * @author Xiuchen
 * @date  2021-01-15 23:29:35
*/

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<string>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<cmath>
#include<math.h>
#include<iostream>
#include<algorithm>
#include<unordered_map>
//#define DEBUG
#define M_PI 3.14159265358979323846
#define dbg(x) cout << #x << " = "<< (x) << endl
#define dbg2(x1,x2) cout << #x1 << " = " << x1 << " " << #x2 << " = " << x2 << endl
#define dbg3(x1,x2,x3) cout<< #x1 << " = " << x1 << " " << #x2 << " = " << x2 << " " << #x3 << " = " << x3 <<endl
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3fLL;
const int maxn = 6e5 + 100;
int gcd(int a, int b){
    return b ? gcd(b, a % b) : a;
}
int tot = 0, nxt[maxn * 7], ver[maxn * 7], head1[maxn], head2[maxn]; ll edge[maxn * 7];
int n, m, vis[maxn];
ll dis1[maxn], dis2[maxn];
struct node
{
    int v; ll c;
    node(int _v = 0, ll _c = 0) : v(_v), c(_c) {}
    bool operator < (const node &r) const{
        return c > r.c;
    }
};
void add(int h[maxn], int x, int y, int z){
    edge[++tot] = z; ver[tot] = y; nxt[tot] = h[x]; h[x] = tot;
}
void Dijkstra(int s, int head[], ll dis[]){
    memset(vis, 0, sizeof(vis));
    for(int i = 1; i <= 3 * n; i++) dis[i] = INF;
    priority_queue<node> q;
    dis[s] = 0;
    q.push(node(s, 0));
    while(q.size()){
        node tmp = q.top(); q.pop();
        int x = tmp.v; ll c = tmp.c;
        if(vis[x] == 1) continue;
        vis[x] = 1;
        for(int i = head[x]; i; i = nxt[i]){
            int y = ver[i], z = edge[i];
            if(dis[y] > dis[x] + z){
                dis[y] = dis[x] + z;
                q.push(node(y, dis[y]));
            }
        }
    }
}
int main(){
#ifdef DEBUG
    freopen("input.txt", "r", stdin);
//	freopen("output.txt", "w", stdout);
#endif
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= m; i++){
        int u, v; ll w; 
        scanf("%d%d%lld", &u, &v, &w);
        //第一个图
        add(head1, u, v, w); add(head1, v, u, w);
        add(head1, u + n, v + n, w); add(head1, v + n, u + n, w);
        add(head1, u + 2 * n, v + 2 * n, w); add(head1, v + 2 * n, u + 2 * n, w);
        add(head1, u, v + n, 0); add(head1, v, u + n, 0);
        add(head1, u + n, v + 2 * n, 2 * w); add(head1, v + n, u + 2 * n, 2 * w);
        //第二个图
        add(head2, u, v, w); add(head2, v, u, w);
        add(head2, u + n, v + n, w); add(head2, v + n, u + n, w);
        add(head2, u + 2 * n, v + 2 * n, w); add(head2, v + 2 * n, u + 2 * n, w);
        add(head2, u, v + n, 2 * w); add(head2, v, u + n, 2 * w);
        add(head2, u + n, v + 2 * n, 0); add(head2, v + n, u + 2 * n, 0);
    }
    Dijkstra(1, head1, dis1);
    Dijkstra(1, head2, dis2);
    for(int i = 2; i <= n; i++) printf("%lld ", min(min(dis1[i + 2 * n], dis2[i + 2 * n]), dis1[i]));
    printf("\n");
    return 0;
}

做法2 :

将图中每一个点的扩展成四个点。
一个点就被分成四个点
node(i, 0, 0), 表示原来的点
node(i, 1, 0)
node(i, 0, 1)
node(i, 1, 1)
d i s [ i ] [ 0 / 1 ] [ 0 / 1 ] dis[i][0/1][0/1] dis[i][0/1][0/1]表示到 i i i点有无使用第一种操作和第二种操作时的最短路径,最终答案就是 d i s [ i ] [ 1 ] [ 1 ] dis[i][1][1] dis[i][1][1]
点经过扩展之后,除了原来的点之间的边,还有扩展之后点之间的边。
node(u, op1, op2) 到 node(v, op1, op2)之间有一条边权为 w w w的边,表示两点之间的边没有进行操作。
node(u, 0, op2) 到 node(v, 1, op2)之间有一条边权为 0 0 0的边,表示两点之间的边进行了操作一。
node(u, op1, 0) 到 node(v, op1, 1)之间有一条边权为 2 ∗ w 2 * w 2w的边,表示两点之间的边进行了操作二。
node(u, 0, 0) 到 node(v, 1, 1)之间有一条边权为 w w w的边,表示两点之间的边同时进行了操作一和二,这时,这条边相当于原来的边权。
这样建完图之后,每个点都成了独立的点,在这个图上跑最短路就行。

代码:

/*
 * @file E.cpp
 * @path D:\code\ACM\codeforces\EDU_102\E.cpp
 * @author Xiuchen
 * @date  2021-01-15 23:29:35
*/

#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<string>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<cmath>
#include<math.h>
#include<iostream>
#include<algorithm>
#include<unordered_map>
//#define DEBUG
#define M_PI 3.14159265358979323846
#define dbg(x) cout << #x << " = "<< (x) << endl
#define dbg2(x1,x2) cout << #x1 << " = " << x1 << " " << #x2 << " = " << x2 << endl
#define dbg3(x1,x2,x3) cout<< #x1 << " = " << x1 << " " << #x2 << " = " << x2 << " " << #x3 << " = " << x3 <<endl
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3fLL;
const int maxn = 2e5 + 100;
int gcd(int a, int b){
    return b ? gcd(b, a % b) : a;
}
int tot = 0, nxt[maxn * 2], ver[maxn * 2], head[maxn]; ll edge[maxn * 2];
int n, m, vis[maxn][2][2];
ll dis[maxn][2][2];
struct node
{
    int v; ll c;
    int op1, op2;
    node(int _v = 0, ll _c = 0, int _op1 = 0, int _op2 = 0) : v(_v), c(_c), op1(_op1), op2(_op2) {}
    bool operator < (const node &r) const{
        return c > r.c;
    }
};
void add(int x, int y, int z){
    edge[++tot] = z; ver[tot] = y; nxt[tot] = head[x]; head[x] = tot;
}
void Dijkstra(int s){
    memset(vis, 0, sizeof(vis));
    memset(dis, 0x3f, sizeof(dis));
    priority_queue<node> q;
    dis[s][0][0] = 0;
    q.push(node(s, 0, 0, 0));
    while(q.size()){
        node tmp = q.top(); q.pop();
        int op1 = tmp.op1, op2 = tmp.op2, x = tmp.v; ll c = tmp.c;
        if(vis[x][op1][op2] == 1) continue;
        vis[x][op1][op2] = 1;
        for(int i = head[x]; i; i = nxt[i]){
            int y = ver[i], z = edge[i];
            if(dis[y][op1][op2] > dis[x][op1][op2] + z){
                dis[y][op1][op2] = dis[x][op1][op2] + z;
                q.push(node(y, dis[y][op1][op2], op1, op2));
            }
            if(op1 == 0 && dis[y][1][op2] > dis[x][op1][op2]){
                dis[y][1][op2] = dis[x][op1][op2];
                q.push(node(y, dis[y][1][op2], 1, op2));
            }
            if(op2 == 0 && dis[y][op1][1] > dis[x][op1][op2] + 2 * z){
                dis[y][op1][1] = dis[x][op1][op2] + 2 * z;
                q.push(node(y, dis[y][op1][1], op1, 1));
            } 
            if(op1 == 0 && op2 == 0 && dis[y][1][1] > dis[x][op1][op2] + z){
                dis[y][1][1] = dis[x][op1][op2] + z;
                q.push(node(y, dis[y][1][1], 1, 1));
            }
        }
    }
}
int main(){
#ifdef DEBUG
    freopen("input.txt", "r", stdin);
//	freopen("output.txt", "w", stdout);
#endif
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= m; i++){
        int u, v; ll w; 
        scanf("%d%d%lld", &u, &v, &w);
        add(u, v, w);
        add(v, u, w);
    }
    Dijkstra(1);
    for(int i = 2; i <= n; i++) printf("%lld ", dis[i][1][1]);
    printf("\n");
    return 0;
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值