【PR #5 A】双向奔赴(状压DP)

双向奔赴

题目链接:PR #5 A

题目大意

给你一个简单无向图,然后你要给每条边定向,每个边每个方向有不一样的费用。
然后要你用最小的费用使得这张图强连通,如果无法强连通输出 -1。

思路

首先考虑最暴力的方法,可以直接找一条路径从 1 出发经过所有点(可以重复经过点),然后回到 1

然后我们简化一下题意,我们可以默认选边权小的那个,然后如果要选另一边就要加上差的费用,然后最后加上边权小和。

然后发现我们其实可以把它分割,分割成若干个环。
具体一点就是你已经有一个强连通的子集,然后你每次从一个子集里面的点往外走,一直走没有见过的,然后最后走回到子集里面,然后你外面见到的点也就也跟着强连通了。(这个一直不走见过的是没问题的,因为走见过的你可以把它拆成若干个没有见过的)
然后就有一个大概的状压想法了:gS 表示当前 S 这个子集强连通的最小费用,fS,i,jS 这个子集强连通,环的终点是 i,现在在 j
(然后为了我们能表示出走出环的点,所以 fS,i,jS 就表示成强连通的子集与现在要加上的点的并集)
然后就是三种,一个是从 gf,接着是 f 内部转移,最后 fg

复杂度为 O(n32n) 能过。

然后你会发现锅了,因为你这样是不能保证一条边只会从一个方向走的。
(就比如你从 g 走一个点到 f 结果你直接从 f 走回 g 了)
那我们考虑怎么办,因为这个时候复杂度已经是很大的了不能再乘 n,考虑压缩一下,会发现你 fS,i,jj 是一定在 S 中的,那我们能不能不在呢?
自然是可以的,那如果我们用不在的方式转移,但是一开始又在呢?
你会发现就不能直接归到答案里面(因为你答案枚举 j 是要不在 S 里面的),而是要通过走一步消除掉这个特异性(因为你无论怎样都是正常转移,那下一个点肯定不在点集里面),所以就可以走回去了。
然后就可以用这个很妙的方法特判掉这个情况啦!
(具体实现可以看代码)

代码

#include<cstdio> #include<cstring> #include<iostream> using namespace std; const int N = 18; int n, a[N][N], f[1 << N][N][N], g[1 << N], ans; int main() { scanf("%d", &n); for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) scanf("%d", &a[i][j]); for (int i = 0; i < n; i++) for (int j = 0; j < i; j++) if (a[i][j] != -1) { int x = min(a[i][j], a[j][i]); a[i][j] -= x; a[j][i] -= x; ans += x; } memset(f, 0x7f, sizeof(f)); memset(g, 0x7f, sizeof(g)); int inf = g[0]; g[1] = 0; for (int S = 1; S < (1 << n); S++) { if (g[S] < inf) { for (int i = 0; i < n; i++) if ((S >> i) & 1) for (int s = 0; s < n; s++) if ((S >> s) & 1) for (int j = 0; j < n; j++) if (!((S >> j) & 1) && a[s][j] != -1) { if (s != i) f[S][i][j] = min(f[S][i][j], g[S] + a[s][j]); else f[S | (1 << j)][i][j] = min(f[S | (1 << j)][i][j], g[S] + a[s][j]);//不能从起点出发到这个点就直接到终点 } } for (int i = 0; i < n; i++) if ((S >> i) & 1) for (int j = 0; j < n; j++) if (f[S][i][j] < inf) for (int k = 0; k < n; k++) if (!((S >> k) & 1) && a[j][k] != -1) f[S | (1 << j)][i][k] = min(f[S | (1 << j)][i][k], f[S][i][j] + a[j][k]); for (int i = 0; i < n; i++) if ((S >> i) & 1) for (int j = 0; j < n; j++) if (!((S >> j) & 1) && f[S][i][j] < inf && a[j][i] != -1) g[S | (1 << j)] = min(g[S | (1 << j)], f[S][i][j] + a[j][i]); } if (g[(1 << n) - 1] < inf) ans += g[(1 << n) - 1]; else ans = -1; printf("%d", ans); return 0; }

__EOF__

本文作者あおいSakura
本文链接https://www.cnblogs.com/Sakura-TJH/p/16412019.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   あおいSakura  阅读(87)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示