Loading

Codeforces Round #703 E. Paired Payment

题意:给定n个结点,m条边的无向连通图。

走过每条路径需要花费(w <= 50)的时间。

每次固定走两步,所耗费的时间为(w1 + w2)²。

输出1到其他所有点的最短时间的最小值。若没有则输出-1。

 

这道题目在网上有两种做法。其思想都是相同的。

参考连接:https://blog.csdn.net/weixin_45697774/article/details/113856213

https://www.cnblogs.com/irty/p/14416524.html

第一种:虚点连边+最短路

对于原图的边我们建立一张新的图。之后将_hash(u, 0)当作不为中间点的点。_hash(u, 1-50)当作作为中间点的点。当一个点连向作为中间点的点时,权值为0。当中间点连向目的点的时候权值为两个权值平方。

具体的描述应该如下:图片源自第二个博客。

 

 

对于每个点都有作为中间点和不作为中间点的情况。最暴力的思路应该就是在最短路里面去O(n2)去枚举两条边,但是n为2e5。这样的复杂度是我们所无法接受的。

所幸的是w只有50,那么我们就不枚举两条边。从而通过建虚点的方式获得上一条边的权值。

这里为了建点方便,就用了一个hash函数。因为权值只有50所以每个点乘上51+w肯定是不会冲突的。

具体的建图方式如上所述。

 

这里放上我的代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e7 + 5;
#define pii pair<int, int>
vector<pair<int, int> > G[maxn];
int dis[maxn];
int n, m;

int _hash(int id, int w) {
    return id * 51 + w;
}

void _add(int u, int v, int w) {
    G[u].push_back({v, w});
}

void add(int u, int v, int w) {
    _add(_hash(u, 0), _hash(v, w), 0);
    for (int i = 1; i <= 50; ++ i) {
        _add(_hash(u, i), _hash(v, 0), (i+w)*(i+w));
    }
}

void dij(int S) {
    priority_queue<pii, vector<pii>, greater<pii> > pq;
    for (int i = 1; i <= maxn-1; ++ i)
        dis[i] = 0x3f3f3f3f;
    pq.push({dis[S] = 0, S});
    while (!pq.empty()) {
        int u = pq.top().second;
        pq.pop();
        for (auto i : G[u]) {
            int v = i.first;
            int w = i.second;
            if (dis[v] > dis[u] + w) {
                pq.push({dis[v] = dis[u] + w, v});
            }
        }
    }
}

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; ++ i) {
        int u, v; int w;
        scanf("%d%d%d", &u, &v, &w);
        add(u, v, w),  add(v, u, w);
    }
    dij(_hash(1, 0));
    for (int i = 1; i <= n; ++ i) {
        if (i != 1) printf(" ");
        if (dis[_hash(i, 0)] == 0x3f3f3f3f) {
            printf("-1");
        }
        else printf("%d", dis[_hash(i, 0)]);
    }
    puts("");
    return 0;
}
View Code

(注意不需要开long long,开了反而会mle的)

 

第二种:多状态最短路

其实就是利用最短路贪心的思想进行的一个dp转移,开一个dis[点数][边权][是否为中间点]的数组。

每次就是有两个状态的推移:

中间点到非中间点:dis[next.id][next.w][0] = min(dis[pre.id][pre.w][1] + 边权)

非中间点到中间点:dis[next.id][next.w][1] = min(dis[pre.id][pre.w][0])

之后O(50n)来记录1到其他n个点,上一条边权为1-50且为非中间点的最短距离。即min(dis[1-n][1-50][0])

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 10;
#define pii pair<int, int>
vector<pair<int, int> > G[maxn];
int dis[maxn][51][2];
int n, m;

void _add(int u, int v, int w) {
    G[u].push_back({v, w});
    G[v].push_back({u, w});
}

struct node {
    int id, pre, w, state;
    bool operator<(const node& p) const{
        return w > p.w;
    }
};

void dij() {
    priority_queue<node> pq;
    memset(dis, 0x3f, sizeof(dis));
    pq.push({1, 0, dis[1][0][0] = 0, 0});
    while (!pq.empty()) {
        node now = pq.top();
        pq.pop();
        int x = now.id;
        for (auto i : G[now.id]) {
            int v = i.first, w = i.second;
            int cost = 0;
            if (now.state == 1) {
                cost = (now.pre + w) * (now.pre + w);
            }
            if (dis[v][w][now.state^1] > dis[now.id][now.pre][now.state] + cost) {
                dis[v][w][now.state^1] = dis[now.id][now.pre][now.state] + cost;
                pq.push({v, w, dis[v][w][now.state^1], now.state^1});
            }
        }
    }
}

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; ++ i) {
        int u, v; int w;
        scanf("%d%d%d", &u, &v, &w);
        _add(u, v, w);
    }
    dij();
    for (int i = 1; i <= n; ++ i) {
        int ans = 0x3f3f3f3f;
        for (int j = 0; j <= 50; ++ j) {
            ans = min(dis[i][j][0], ans);
        }
        if (i != 1) printf(" ");
        if (ans == 0x3f3f3f3f) printf("-1");
        else printf("%d", ans);
    }
    puts("");
    return 0;
}
View Code
posted @ 2021-03-07 22:23  ViKyanite  阅读(100)  评论(0编辑  收藏  举报