F - Negative Traveling Salesman

F - Negative Traveling Salesman

Problem Statement

There is a weighted simple directed graph with N vertices and M edges. The vertices are numbered 1 to N, and the i-th edge has a weight of Wi and extends from vertex Ui to vertex Vi. The weights can be negative, but the graph does not contain negative cycles.

Determine whether there is a walk that visits each vertex at least once. If such a walk exists, find the minimum total weight of the edges traversed. If the same edge is traversed multiple times, the weight of that edge is added for each traversal.

Here, "a walk that visits each vertex at least once" is a sequence of vertices v1,v2,,vk that satisfies both of the following conditions:

  • For every i (1ik1), there is an edge extending from vertex vi to vertex vi+1.
  • For every j (1jN), there is i (1ik) such that vi=j.

Constraints

  • 2N20
  • 1MN(N1)
  • 1Ui,ViN
  • UiVi
  • (Ui,Vi)(Uj,Vj) for ij
  • 106Wi106
  • The given graph does not contain negative cycles.
  • All input values are integers.

Input

The input is given from Standard Input in the following format:

N M
U1 V1 W1
U2 V2 W2

UM VM WM

Output

If there is a walk that visits each vertex at least once, print the minimum total weight of the edges traversed. Otherwise, print No.


Sample Input 1

3 4
1 2 5
2 1 -3
2 3 -4
3 1 100

Sample Output 1

-2

By following the vertices in the order 2123, you can visit all vertices at least once, and the total weight of the edges traversed is (3)+5+(4)=2. This is the minimum.


Sample Input 2

3 2
1 2 0
2 1 0

Sample Output 2

No

There is no walk that visits all vertices at least once.


Sample Input 3

5 9
1 2 -246288
4 5 -222742
3 1 246288
3 4 947824
5 2 -178721
4 3 -947824
5 4 756570
2 5 707902
5 1 36781

Sample Output 3

-449429

 

解题思路

  之前在洛谷上做过类似的题目:P8733 [蓝桥杯 2020 国 C] 补给,所以比赛的时候一下就做出来了。

  看到数据范围容易想到状压 dp,定义 f(i,j) 表示从起点走到当前点 j 且经过的节点所表示的二进制集合为 i 的所有路径中权值的最小值,状态转移方程为 f(i,j)=minki{f(i{k},k)+g(k,j)}。其中 g(k,j)kj 的最短距离,任意两点间的最短路可以通过 Floyd 算法求得。另外 kj 所经过的中间节点(不含 kj)不会被加入到集合中,这里每次只会加入最后到达的节点。

  为什么这样做是正确的呢?首先上面的做法本质是找到一个 0n1 的排列 p,使得 i=1n1g(i1,i) 最小,这条路径必然会经过每个节点至少一次。而最优解所对应的路径中,节点 0n1 也必然出现至少一次,对于每个编号的节点从中任选出一个(路径的起点和终点必选),那么整个路径就会被分成 n1 段,显然每一段的长度必然是对应两端点的最短路径,否则就可以用更短的路径来替换,与最优解矛盾了。

  最后的答案为 min0in1{f(2n1,i)}

  AC 代码如下,时间复杂度为 O(n3+2nn2)

#include <bits/stdc++.h>
using namespace std;

const int N = 25, M = 1 << 20;

int g[N][N];
int f[M][N];

int main() {
    int n, m;
    scanf("%d %d", &n, &m);
    memset(g, 0x3f, sizeof(g));
    for (int i = 1; i <= n; i++) {
        g[i][i] = 0;
    }
    while (m--) {
        int u, v, w;
        scanf("%d %d %d", &u, &v, &w);
        u--, v--;
        g[u][v] = min(g[u][v], w);
    }
    for (int k = 0; k < n; k++) {
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                g[i][j] = min(g[i][j], g[i][k] + g[k][j]);
            }
        }
    }
    memset(f, 0x3f, sizeof(f));
    for (int i = 0; i < n; i++) {
        f[1 << i][i] = 0;
    }
    for (int i = 1; i < 1 << n; i++) {
        for (int j = 0; j < n; j++) {
            if (i >> j & 1) {
                for (int k = 0; k < n; k++) {
                    if (j == k) continue;
                    if (i >> k & 1) {
                        f[i][j] = min(f[i][j], f[i ^ 1 << j][k] + g[k][j]);
                    }
                }
            }
        }
    }
    int ret = 0x3f3f3f3f;
    for (int i = 0; i < n; i++) {
        ret = min(ret, f[(1 << n) - 1][i]);
    }
    if (ret >= 0x3f3f3f3f >> 1) printf("No");
    else printf("%d", ret);
    
    return 0;
}

  ps:这题还可以建分层图然后跑 spfa,一开始用 std::array<int, 2> 来存二元组结果 TLE 了,然后改成 std::pair<int, int> 刚好卡过去,最慢的数据跑了 5989 ms,挺哈人的()

  AC 代码如下,最坏情况下的时间复杂度为 O(2nn3)

#include <bits/stdc++.h>
using namespace std;

typedef pair<int, int> PII;

const int N = 25, M = 1 << 20;

int g[N][N];
int dist[M][N];
bool vis[M][N];

int main() {
    int n, m;
    scanf("%d %d", &n, &m);
    memset(g, 0x3f, sizeof(g));
    while (m--) {
        int u, v, w;
        scanf("%d %d %d", &u, &v, &w);
        u--, v--;
        g[u][v] = min(g[u][v], w);
    }
    queue<PII> q;
    memset(dist, 0x3f, sizeof(dist));
    for (int i = 0; i < n; i++) {
        q.push({1 << i, i});
        dist[1 << i][i] = 0;
        vis[1 << i][i] = true;
    }
    while (!q.empty()) {
        auto p = q.front();
        q.pop();
        vis[p.first][p.second] = false;
        for (int i = 0; i < n; i++) {
            if (dist[p.first | 1 << i][i] > dist[p.first][p.second] + g[p.second][i]) {
                dist[p.first | 1 << i][i] = dist[p.first][p.second] + g[p.second][i];
                if (!vis[p.first | 1 << i][i]) {
                    vis[p.first | 1 << i][i] = true;
                    q.push({p.first | 1 << i, i});
                }
            }
        }
    }
    int ret = 0x3f3f3f3f;
    for (int i = 0; i < n; i++) {
        ret = min(ret, dist[(1 << n) - 1][i]);
    }
    if (ret >= 0x3f3f3f3f >> 1) printf("No");
    else printf("%d", ret);
    
    return 0;
}

 

参考资料

  Editorial - AtCoder Beginner Contest 338:https://atcoder.jp/contests/abc338/editorial/9180

posted @   onlyblues  阅读(4)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
历史上的今天:
2023-01-30 F. Timofey and Black-White Tree
Web Analytics
点击右上角即可分享
微信分享提示