UVa 1440 只有下界的最小流

本文介绍了一种解决UVa1440题目的方法,该题目要求找到有向图中所有边至少被经过一次所需的最少路径数量。文章首先介绍了建图策略,然后详细解释了如何通过求解最小流来解决问题,最后给出了输出路径的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

[置顶] UVa 1440 只有下界的最小流

分类: 图论 UVa && LA   112人阅读  评论(0)  收藏  举报

题意:

给你一幅有向图,你每次可以从任意点出发。图中的每条边至少要经过一次,问你至少要走几次。


建图:

设每个点i的入度减去出度为d[i], S为源点,T为汇点。

对于d[i] > 0的点i, 连边<i,T>

对于d[i] < 0的点i, 连边<S,i>

其它边连法与输入的边相同。


问题:

对于输入的每条边下界为1,我们要求的是最小值,所以问题可以转化为求该图的最小流,

显然,对于没有下界的网络 其最小流就为零, 但该题的图有下界,那么如何求有下界的网络的最小流呢?


只有下界的最小流的解法:

先判断该网络是否存在可行流,对于本题,显然存在可行流。

如果可行流存在(假设可行流的值为f1),则从可行流出发,倒向求解。
即保持网络中的弧方向不变,将T作为源点,S作为汇点,反向流一次最大流后(假设求出的最大流为f2),

那么原图的最小流为f1 - f2。


输出路径:

求完该图的最小流以后,假如边<i,j>流了c流量,那么说明边<i,j>需要走c+1次。

对于以上的情况,新建一幅要求路径的图。

你可以建c+1条边<i,j>, 也可以加一条边<i,j>和一个信息(访问次数c+1)。

我用了后者的作法,但在理论上我们把它看成c+1条边。

我们可以计算出这幅图每个点的d[i](入度减去出度)。

对于每个d[i]点,多次dfs搜索路径,每次搜一条路,并把这条路标记掉(下次不访问),并且更新这条路径所经过的点的d[i]值(其实就是,起点的出度-1, 终点的入度-1,其它点不变), 这里要注意有可能没有边可以走,那么只能搜到一个点,这种情况不用输出也不用更新d[i]

每次搜到一条路就停止dfs,然后输出路径。


代码:

[cpp]  view plain copy
  1. #include <cstdio>  
  2. #include <cstring>  
  3. #include <algorithm>  
  4. #include <vector>  
  5. using namespace std;  
  6. const int maxn = 110;  
  7. const int inf = 1e9;  
  8. #define PB push_back  
  9. #define MP make_pair  
  10. #define PII pair <int, int>  
  11. #define X first  
  12. #define Y second  
  13. struct Edge {  
  14.     int v, c, next;  
  15.     Edge() {}  
  16.     Edge(int v, int c, int next) : v(v), c(c), next(next) {}  
  17. } edge[maxn * maxn << 1];  
  18.   
  19. int head[maxn], E;  
  20. int n, m;  
  21. int S, T;  
  22. void init() {  
  23.     E = 0;  
  24.     memset(head, -1, sizeof(head));  
  25. }  
  26.   
  27. void add(int s, int t, int c) {  
  28.     edge[E] = Edge(t, c, head[s]);  
  29.     head[s] = E++;  
  30.     edge[E] = Edge(s, 0, head[t]);  
  31.     head[t] = E++;  
  32. }  
  33. int d[maxn];  
  34. vector<PII> edges[maxn];  
  35.   
  36. int gap[maxn], dis[maxn], pre[maxn], cur[maxn];  
  37. int sap(int s, int t, int n) {  
  38.     int i;  
  39.     for (i = 0; i <= n; i++) {  
  40.         cur[i] = head[i];  
  41.         gap[i] = dis[i] = 0;  
  42.     }  
  43.     gap[0] = n;  
  44.     int u = pre[s] = s, aug = inf, maxf = 0, v;  
  45.     while (dis[s] < n) {  
  46.         loop: for (i = cur[u]; ~i; i = edge[i].next) {  
  47.             v = edge[i].v;  
  48.             if (edge[i].c && dis[u] == dis[v] + 1) {  
  49.                 aug = min(aug, edge[i].c);  
  50.                 pre[v] = u;  
  51.                 cur[u] = i;  
  52.                 u = v;  
  53.                 if (u == t) {  
  54.                     while (u != s) {  
  55.                         u = pre[u];  
  56.                         edge[cur[u]].c -= aug;  
  57.                         edge[cur[u] ^ 1].c += aug;  
  58.                     }  
  59.                     maxf += aug;  
  60.                     aug = inf;  
  61.                 }  
  62.                 goto loop;  
  63.             }  
  64.         }  
  65.         int d = n;  
  66.         for (i = head[u]; ~i; i = edge[i].next) {  
  67.             v = edge[i].v;  
  68.             if (edge[i].c && d > dis[v]) {  
  69.                 d = dis[v];  
  70.                 cur[u] = i;  
  71.             }  
  72.         }  
  73.         if (!(--gap[dis[u]]))  
  74.             break;  
  75.         ++gap[dis[u] = d + 1];  
  76.         u = pre[u];  
  77.     }  
  78.     return maxf;  
  79. }  
  80. bool flag;  
  81. int res[maxn], t;  
  82. int ans, cnt;  
  83. void dfs(int u) {  
  84.     int i;  
  85.     bool g = 0;  
  86.     res[t++] = u;  
  87.     for (i = 0; i < (int) edges[u].size(); i++) {  
  88.         int v = edges[u][i].X;  
  89.         int &vis = edges[u][i].Y;  
  90.         if (flag) return;  
  91.         if (!vis) continue;  
  92.         g = 1; cnt++; vis--;  
  93.         dfs(v);  
  94.     }  
  95.     if (!g) d[u]--, flag = 1;  
  96. }  
  97. int main() {  
  98.     int i, j, k, x;  
  99.     while (~scanf("%d", &n)) {  
  100.         memset(d, 0, sizeof(d));  
  101.         ans = 0;  
  102.         init();  
  103.         for (i = 0; i < n; i++) {  
  104.             scanf("%d", &k);  
  105.             while (k--) {  
  106.                 scanf("%d", &x);  
  107.                 d[--x]++;  
  108.                 d[i]--;  
  109.                 add(i, x, inf);  
  110.             }  
  111.         }  
  112.         S = n; T = n + 1;  
  113.         for (i = 0; i < n; i++)  
  114.             if (d[i] < 0)  
  115.                 add(i, T, -d[i]), ans -= d[i];  
  116.             else if (d[i] > 0)  
  117.                 add(S, i, d[i]);  
  118.   
  119.         ans -= sap(S, T, T + 1);  
  120.         printf("%d\n", ans);  
  121.         for (i = 0; i < n; i++)  
  122.             edges[i].clear();  
  123.         for (i = 0; i < n; i++)  
  124.             for (j = head[i]; ~j; j = edge[j].next)  
  125.                 if (!(j & 1)) {  
  126.                     int v = edge[j].v;  
  127.                     if (v >= n)  
  128.                         continue;  
  129.                     edges[i].PB(MP(v, edge[j ^ 1].c + 1));  
  130.                     d[i] -= edge[j ^ 1].c;  
  131.                     d[v] += edge[j ^ 1].c;  
  132.                 }  
  133.   
  134.         for (i = 0; i < n; i++)  
  135.             while (d[i] < 0) {  
  136.                 t = 0;  
  137.                 cnt = 0;  
  138.                 flag = 0;  
  139.                 d[i]++;  
  140.                 dfs(i);  
  141.   
  142.                 for (j = 0; j < t; j++) {  
  143.                     printf("%d", res[j] + 1);  
  144.                     if (j == t - 1)  
  145.                         puts("");  
  146.                     else  
  147.                         printf(" ");  
  148.                 }  
  149.             }  
  150.     }  
  151.     return 0;  
  152. }  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值