Cocoicobird
热爱永远可以成为你继续下去的理由

牛客刷题-Day40

今日刷题:\(1028-1032\)

1028 NC14700 追债之旅

题目描述

小明现在要追讨一笔债务,已知有 \(n\) 座城市,每个城市都有编号,城市与城市之间存在道路相连(每条道路都是双向的),经过任意一条道路需要支付费用。小明一开始位于编号为 \(1\) 的城市,欠债人位于编号为 \(n\) 的城市。小明每次从一个城市到达另一个城市需要耗时 \(1\) 天,而欠债人每天都会挥霍一定的钱,等到第 \(k\) 天后(即第 \(k+1\) 天)他就会离开城 \(n\) 并再也找不到了。小明必须要在他离开前抓到他(最开始为第 \(0\) 天),同时希望自己的行程花费和欠债人挥霍的钱的总和最小,你能帮他计算一下最小总和吗?

输入描述

\(1\) 行输入三个整数 \(n\)\(m\)\(k\),代表城市数量,道路数量和指定天数
\(2-m+1\) 行,每行输入三个整数 \(u\)\(v\)\(w\),代表起点城市,终点城市和支付费用。(数据保证无重边,自环)
\(m+2\) 行输入 \(k\) 个整数,第 \(i\) 个整数 \(a_i\) 代表第 \(i\) 天欠债人会挥霍的钱。
数据保证:\(0<n≤1000\)\(0<m≤10000\)\(0<k≤10\)\(1≤u,v≤n\)\(0<w,a_i≤1000\)

输出描述

输出一行,一个整数,代表小明的行程花费和欠债人挥霍的钱的最小总和,如果小明不能抓住欠债人(即不能在第 \(k\) 天及之前到达城 \(n\)),则输出 \(-1\)

示例1

输入

3 3 2
1 3 10
1 2 2
2 3 2
3 7

输出

13

说明:小明从 \(1-3\),总共费用\(=10\)(行程)\(+3\)(挥霍费用)\(=13\),是方案中最小的(另一条方案花费 \(14\))。

示例2

输入

3 2 1
1 2 3
2 3 3
10

输出

-1

说明:小明无法在第 \(1\) 天赶到城 \(3\),所以输出 \(-1\)

解题思路

根据题意,这是有边数限制的最短路问题,可以使用 \(Bellman-Ford\) 算法,这里在第 \(i\) 次迭代时,\(dist[v]\) 表示恰好经过 \(i\) 步的最小代价,每次迭代前需要对 \(dist\) 进行重新赋值,若不迭代,则表示不超过 \(i\) 步的最小代价。这里的迭代次数是会产生费用的,因此 \(dist\) 数组需要和次数关联。

C++ 代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1010, M = 10010;

int n, m, k;
int h[N], e[M], ne[M], w[M], idx;
int a[N];
int dist[N], last[N];

struct Node {
    int a, b, c;
} edges[M * 2];

int main() {
    scanf("%d%d%d", &n, &m, &k);
    for (int i = 1; i <= m; i++) {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        edges[i] = {a, b, c};
        edges[i + m] = {b, a, c};
    }
    for (int i = 1; i <= k; i++)
        scanf("%d", &a[i]);
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    int ans = 0x3f3f3f3f;
    for (int i = 1; i <= k; i++) {
        memcpy(last, dist, sizeof dist);
        memset(dist, 0x3f, sizeof dist);
        for (int j = 1; j <= m * 2; j++) {
            int u = edges[j].a, v = edges[j].b, w = edges[j].c;
            if (last[u] < 0x3f3f3f3f) {
                dist[v] = min(dist[v], last[u] + w + a[i]);
            }
        }
        ans = min(ans, dist[n]);
    }
    if (ans >= 0x3f3f3f3f)
        puts("-1");
    else
        printf("%d", ans);
    return 0;
}

1029-1030 NC16611 [NOIP2009]最优贸易

题目描述

C 国有 \(n\) 个大城市和 \(m\) 条道路,每条道路连接这 \(n\) 个城市中的某两个城市。任意两个城市之间最多只有一条道路直接相连。这 \(m\) 条道路中有一部分为单向通行的道路,一部分为双向通行的道路,双向通行的道路在统计条数时也计为 \(1\) 条。

C 国幅员辽阔,各地的资源分布情况各不相同,这就导致了同一种商品在不同城市的价格不一定相同。但是,同一种商品在同一个城市的买入价和卖出价始终是相同的。

商人阿龙来到 C 国旅游。当他得知同一种商品在不同城市的价格可能会不同这一信息之后,便决定在旅游的同时,利用商品在不同城市中的差价赚回一点旅费。设 C 国 \(n\) 个城市的标号从 \(1-n\),阿龙决定从 \(1\) 号城市出发,并最终在 \(n\) 号城市结束自己的旅行。在旅游的过程中,任何城市可以重复经过多次,但不要求经过所有 \(n\) 个城市。阿龙通过这样的贸易方式赚取旅费:他会选择一个经过的城市买入他最喜欢的商品——水晶球,并在之后经过的另一个城市卖出这个水晶球。用赚取的差价当作旅费。由于阿龙主要是来 C 国旅游,他决定这个贸易只进行最多一次。当然,在赚不到差价的情况下它就无需进行贸易。

假设 C 国有 \(5\) 个大城市,城市的编号和道路连接情况如下图,单向箭头表示这条道路为单向通行。双向箭头表示这条道路为双向通行。
image

假设 \(1~n\) 号城市的水晶球价格分别为 \(4\)\(3\)\(5\)\(6\)\(1\)

阿龙可以选择如下一条线路:\(1->2->3->5\),并在 \(2\) 号城市以 \(3\) 的价格买入水晶球,在 \(3\) 号城市以 \(5\) 的价格卖出水晶球,赚取的旅费数为 \(2\)

阿龙也可以选择如下一条线路:\(1->4->5->4->5\),并在第 \(1\) 次到达 \(5\) 号城市时以 \(1\) 的价格买入水晶球,在第 \(2\) 次到达 \(4\) 号城市时以 \(6\) 的价格卖出水晶球,赚取的旅费数为 \(5\)

现在给出 \(n\) 个城市的水晶球价格,\(m\) 条道路的信息(每条道路所连接的两个城市的编号以及该条道路的通行情况)。请你告诉阿龙,他最多能赚钱多少旅费。

输入描述

第一行包含 \(2\) 个正整数 \(n\)\(m\),中间用一个空格隔开,分别表示城市的数目和道路的数目。
第二行 \(n\) 个正整数,每两个正整数之间用一个空格隔开,按标号顺序分别表示这 \(n\) 个城市的商品价格。
接下来 \(m\) 行,每行有 \(3\) 个正整数,\(x\)\(y\)\(z\),每两个整数之间用一个空格隔开。如果 \(z=1\),表示这条道路是城市 \(x\) 到城市 \(y\) 之间的单向道路;如果 \(z=2\),表示这条道路为城市 \(x\) 和城市 \(y\) 之间的双向道路。

输出描述

\(1\) 行,包含 \(1\) 个整数,表示最多能赚取的旅费。如果没有进行贸易,则输出 \(0\)

示例

输入

5 5
4 3 5 6 1
1 2 1
1 4 1
2 3 2
3 5 1
4 5 2

输出

5
备注

输入数据保证 \(1\) 号城市可以到达 \(n\) 号城市。
对于 \(10\%\) 的数据,\(1≤n≤6\)
对于 \(30\%\) 的数据,\(1≤n≤100\)
对于 \(50\%\) 的数据,不存在一条旅游路线,可以从一个城市出发,再回到这个城市。
对于 \(100\%\) 的数据,\(1≤n≤100000\)\(1≤m≤500000\)\(1≤x,y≤n\)\(1≤z≤2\)\(1≤\)各城市水晶球价格\(≤100\)

解题思路

分层图+\(SPFA\)。因为最多进行一次消费操作,因此总共三种状态,即未进行消费,已买入和已卖出。从未消费到已买入,则代价是负值,从已买入到已卖出代价是正值,以此建立分层图,每层之间状态不变,边权为零,否则按照前面的描述进行边权的赋值。代码 add(i, i + n, -a[i]) 表示的是在 \(i\) 点进行了买入操作,因此建立一个负权边。最后整个图是存在负权边的,因此需要通过 \(Bellman-Ford\) 算法进行计算最短路,通过队列优化即位 \(SPFA\)

C++ 代码
#include <bits/stdc++.h>
using namespace std;
const int N = 100010, M = N * 5;

int n, m, a[N];
int h[N * 3], e[M * 6 + N * 2], ne[M * 6 + N * 2], w[M * 6 + N * 2], idx;
int dist[N * 3], st[N * 3];

void add(int x, int y, int z) {
    e[idx] = y, w[idx] = z, ne[idx] = h[x], h[x] = idx++;
}

void spfa() {
    memset(dist, 128, sizeof dist);
    queue<int> q;
    dist[1] = 0;
    q.push(1);
    st[1] = true;
    while (q.size()) {
        auto 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;
                }
            }
        }
    }
}

int main() {
    memset(h, -1, sizeof h);
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++)
        scanf("%d", &a[i]);
    for (int i = 0; i < m; i++) {
        int x, y, z;
        scanf("%d%d%d", &x, &y, &z);
        if (z == 1) {
            add(x, y, 0);
            add(x + n, y + n, 0);
            add(x + n * 2, y + n * 2, 0);
        } else {
            add(x, y, 0);
            add(y, x, 0);
            add(x + n, y + n, 0);
            add(y + n, x + n, 0);
            add(x + n * 2, y + n * 2, 0);
            add(y + n * 2, x + n * 2, 0);
        }
    }
    for (int i = 1; i <= n; i++) {
        add(i, i + n, -a[i]);
        add(i + n, i + n * 2, a[i]);
    }
    spfa();
    printf("%d", dist[n * 3]);
    return 0;
}

1031 NC22594 Rinne Loves Graph

题目描述

Island 发生了一场暴乱!现在 Rinne 要和 Setsuna 立马到地上世界去。
众所周知:Island 是有一些奇怪的城镇和道路构成的(题目需要,游戏党勿喷),有些城镇之间用双向道路连接起来了,且每条道路有它自己的距离。但是有一些城镇已经被派兵戒严,虽然主角可以逆天改命强闯,但是为了体验该游戏的平衡性,他们只能穿过不超过 \(K\) 次被戒严的城镇。
定义“穿过”:从一个戒严的点出发到达任意一个点,都会使得次数加1
现在他们想从 \(1\) 号城镇最快的走到 \(n\) 号城镇(即出口),现在他们想让你告诉他们最短需要走多少路。

输入描述

第一行三个整数 \(n,m,k\),分别表示城镇数量,边数量和最多能闯过被戒严的城市的次数。
接下来 \(n\) 行,每行一个整数 \(1\)\(0\),如果为 1 则表示该城市已被戒严,\(0\) 则表示相反。
接下来 \(m\) 行,每行三个数 \(u,v,w\),表示在 \(u\) 城镇和 \(v\) 城镇之间有一条长度为 \(w\) 的双向道路。

输出描述

输出一行一个数字,表示从 \(1\)\(n\) 的最短路径,如果到达不了的话,请输出 \(-1\)

示例

输入

4 5 1
1
0
1
0
1 2 10
2 3 10
3 1 15
1 4 60
3 4 30

输出

60
备注

\(2≤n≤800,1≤m≤4000,1≤k≤10,1≤w≤10^6\)
保证没有多条道路连接同一对城市,也没有一条道路连向自己。

解题思路

分层图+\(Dijkstra\)。如果当前是戒严点,则从该点到其余点会消耗一次“穿过”次数,则需要分层;否则对于非戒严点,其到其余各点不会消耗次数,也就是状态不会变,因此是同一层的,具体建图见代码。

C++ 代码
#include <bits/stdc++.h>
using namespace std;
const int N = 10010, M = N * 10;
typedef pair<int, int> PII;

int n, m, k, a[N];
int h[N], e[M], ne[M], w[M], idx;
int dist[N], st[N];

void add(int x, int y, int z) {
    e[idx] = y, w[idx] = z, ne[idx] = h[x], h[x] = idx++;
}

void dijkstra() {
    memset(dist, 127, sizeof dist);
    priority_queue<PII, vector<PII>, greater<PII>> pq;
    dist[1] = 0;
    pq.push({dist[1], 1});
    while (pq.size()) {
        auto t = pq.top();
        pq.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];
                pq.push({dist[j], j});
            }
        }
    }
}

int main() {
    memset(h, -1, sizeof h);
    cin >> n >> m >> k;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    for (int i = 0; i < m; i++) {
        int x, y, z;
        cin >> x >> y >> z;
        if (!a[x]) // 非戒严
            for (int j = 0; j <= k; j++)
                add(x + j * n, y + j * n, z);
        if (!a[y])
            for (int j = 0; j <= k; j++)
                add(y + j * n, x + j * n, z);
        if (a[x]) // 戒严
            for (int j = 0; j < k; j++)
                add(x + j * n, y + (j + 1) * n, z);
        if (a[y])
            for (int j = 0; j < k; j++)
                add(y + j * n, x + (j + 1) * n, z);
    }
    dijkstra();
    int ans = 1 << 30;
    for (int i = 0; i <= k; i++)
        ans = min(ans, dist[n + i * n]);
    if (ans >= 1 << 30)
        ans = -1;
    cout << ans << endl;
    return 0;
}

1032 NC22600 Rinne Loves Dynamic Graph

题目描述

Rinne 学到了一个新的奇妙的东西叫做动态图,这里的动态图的定义是边权可以随着操作而变动的图。
当我们在这个图上经过一条边的时候,这个图上所有边的边权都会发生变动。
定义变动函数 \(f(x)=\frac{1}{1-x}\),表示我们在图上走过一条边后,图的边权变动情况。
这里指的“图的变动”的意思是将每条边的边权代入上函数,得到的值即为该次变动后的边权。
现在 Rinne 想要知道,在这个变动的图上从 \(1\)\(n\) 的最短路径。
因为 Rinne 不喜欢负数,所以她只需要你输出经过的边权权值绝对值之和最小的那个值就可以了。
输出答案保留三位小数。

输入描述

第一行两个正整数 \(N,M\),表示这个动态图的点数和边数。
接下来 \(M\) 行,每行三个正整数 \(u,v,w\),表示存在一条连接点 \(u,v\) 的无向边,且初始权值为 \(w\)

输出描述

如果能到达的话,输出边权绝对值之和最小的答案,保留三位小数。
否则请输出 \(-1\)

示例

输入

3 3
1 2 2
2 3 2
3 1 3

输出

3.000

说明:走 \(1→2→3\),总花费 \(2+∣\frac{1}{1-2}∣=3\)

备注

\(n≤100000,m≤300000,2≤x≤1000\)

解题思路

分层图+\(Dijkstra\)。这里需要进行推导,\(x\rightarrow \frac{1}{1-x}\rightarrow \frac{x-1}{x}\rightarrow x\),也就是经过三次变动,边权会变回原来的值。因此利用分层图,每经过一条边,相当于从当前层到下一层,具体建图见代码。

C++ 代码
#include <bits/stdc++.h>
using namespace std;
const int N = 100010, M = 600010;
typedef pair<double, int> PDI;

int n, m;
int h[N * 3], e[M * 3], ne[M * 3], idx;
double w[M * 3];
double dist[N * 3];
bool st[N * 3];

void add(int x, int y, double z) {
    e[idx] = y, w[idx] = z, ne[idx] = h[x], h[x] = idx++;
}

void dijkstra() {
    priority_queue<PDI, vector<PDI>, greater<PDI>> pq;
    for (int i = 0; i < N * 3; i++)
        dist[i] = 1e18;
    dist[1] = 0;
    pq.push({dist[1], 1});
    while (pq.size()) {
        auto t = pq.top();
        pq.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];
                pq.push({dist[j], j});
            }
        }
    }
}

int main() {
    memset(h, -1, sizeof h);
    cin >> n >> m;
    for (int i = 0; i < m; i++) {
        int x, y;
        double z;
        cin >> x >> y >> z;
        add(x, y + n, z);
        add(y, x + n, z);
        z = 1.0 / (1 - z);
        add(x + n, y + 2 * n, fabs(z));
        add(y + n, x + 2 * n, fabs(z));
        z = 1.0 / (1 - z);
        add(x + 2 * n, y, fabs(z));
        add(y + 2 * n, x, fabs(z));
    }
    dijkstra();
    double ans = 1e18;
    for (int i = 0; i <= 2; i++)
        ans = min(ans, dist[n + i * n]);
    if (ans == 1e18) {
        puts("-1");
    } else {
        printf("%.3lf", ans);
    }
    return 0;
}
posted on 2026-05-08 16:08  Cocoicobird  阅读(11)  评论(0)    收藏  举报