BZOJ 2069: [POI2004]ZAW(Dijkstra + 二进制拆分)

题意

给定一个有 N 个点 M 条边的无向图, 每条无向边 最多只能经过一次 .

对于边 (u,v) , 从 uv 的代价为 a , 从 vu 的代价为 b , 其中 ab 不一定相等.

求一个包含 1 号点的有向环, 使得环上代价之和最小.

N3×104,M105,1a,b104 , 保证没有重边和自环 .

题解

考虑一条包含 1 的有向环, 一定是 1xy1 这样子. (xy)

那么我们可以考虑一个很显然的暴力:枚举 x,y 然后做最短路, 但是这样显然太慢了.

但是这里的最短路是可以 “并行” 地求的. 也就是说, 如果给定两个不相交的点集 A,B , 那么我们可以用一次最短路的时间求出所
有点对 (x,y) 满足 xA,yB 的最短路的最小值.

具体地, 我们把 1 号点拆成两个点, 一个作为源点只连向 A 中的点, 另一个作为汇点只被 B 中的点连向.

然后这里需要一个二进制拆分的技巧: 在与 1 相邻的那些点中,每次考虑它们二进制下的第 k 位, 将这一位为 0 的放入 A , 为 1 的放入 B , 那么只需 logN 次, 我们便可以考虑到每一对.

以上全部摘自 __debug 的 PPT 。

这个最短路可以用 Spfa 求,但实测要比 Dijkstra 慢几倍。。为了求稳,还是用 Dijkstra 吧233

所以最后的复杂度就是 O((N+M)log2N)

总结

对于一类考虑点对贡献,并且很多对可以并行求,且重复计算没有影响的问题,能考虑二进制拆分技巧,对于每一位分别考虑。

将整体分成两组,最后计算贡献,能大幅度降低时间复杂度。

新套路 get

代码

特别好写233

#include <bits/stdc++.h> #define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i) #define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i) #define Set(a, v) memset(a, v, sizeof(a)) #define Cpy(a, b) memcpy(a, b, sizeof(a)) #define debug(x) cout << #x << ": " << x << endl #define DEBUG(...) fprintf(stderr, __VA_ARGS__) #define fir first #define sec second #define mp make_pair using namespace std; inline bool chkmin(int &a, int b) {return b < a ? a = b, 1 : 0;} inline bool chkmax(int &a, int b) {return b > a ? a = b, 1 : 0;} inline int read() { int x = 0, fh = 1; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') fh = -1; for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ 48); return x * fh; } void File() { #ifdef zjp_shadow freopen ("2069.in", "r", stdin); freopen ("2069.out", "w", stdout); #endif } const int N = 5010, M = 10100 * 2, inf = 0x7f7f7f7f; int Head[N], Next[M], to[M], val[M], e = 0; inline void add_edge(int u, int v, int w) { to[++ e] = v; Next[e] = Head[u]; Head[u] = e; val[e] = w; } priority_queue<pair<int, int> > P; int dis[N], S, T; bitset<N> vis; int Dijkstra() { Set(dis, inf); dis[S] = 0; P.push(mp(0, S)); vis.reset(); while (!P.empty()) { int u = P.top().sec; P.pop(); if (vis[u]) continue ; vis[u] = true; for (int i = Head[u]; i; i = Next[i]) { int v = to[i]; if (chkmin(dis[v], dis[u] + val[i])) P.push(mp(- dis[v], v)); } } return dis[T]; } struct Edge { int u, v, a, b; } lt[M]; int n, m; void Rebuild(int cur, int flag) { Set(Head, 0); e = 0; S = 1; T = n + 1; For (i, 1, m) { int u = lt[i].u, v = lt[i].v, a = lt[i].a, b = lt[i].b; if (u == 1) { if ((v & cur) ^ flag) add_edge(S, v, a); else add_edge(v, T, b); } else add_edge(u, v, a), add_edge(v, u, b); } } int main () { File(); n = read(); m = read(); For (i, 1, m) { int u = read(), v = read(), a = read(), b = read(); if (u > v) swap(u, v), swap(a, b); lt[i] = (Edge) {u, v, a, b}; } int ans = inf; for (int bit = 1; bit <= n; bit <<= 1) { Rebuild(bit, 0), chkmin(ans, Dijkstra()); Rebuild(bit, bit), chkmin(ans, Dijkstra()); } printf ("%d\n", ans); return 0; }

__EOF__

本文作者zjp_shadow
本文链接https://www.cnblogs.com/zjp-shadow/p/9561449.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   zjp_shadow  阅读(354)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示