P2770 航空路线问题(最大费用最大流)
作者:互联网
题意:给一个图,一个起点,一个终点,要找到一条从起点到终点再回到起点的一条回路,
且每个点除起点只经过一次,且路径最长。
【题目描述】
给出一张有向图,每个点(除了起点 1)每条边都只能经过一次,求出从 1 到 n 在回到 1 的一条路径,使得经过的点个数最大,并输出路径。
【数据范围】
100\%100% n \leqslant 100n⩽100
【分析】
这是一道网络瘤题目。
那么,如何建模呢?
【建模】
俗话说得好啊:网络瘤,网络瘤,网络建模最毒瘤。 稍微一不注意踩到了坑就 GG 。
把题意转换一下,实际上是求从 11 到 nn 的两条互不相交的路径。 限制条件是除起点、终点外的每条边、每个点只能经过一次,那么就可以进行拆点,把点可以经过的最大次数作为点内部的流量,节点数作为点内部的费用,最后用 MCMF求一个最大流最大花费。
求出的最大流就是所找到的路径数,如果它小于等于 11,就说明找不到这样一条路径。 但有一种特殊情况需要特判:起点、终点只有一条边相连,这时候虽然只能找到一条路径,可 1 能直通 n 并直接回来,是合法的路径。
然后就是 美 (sang)(sang) 妙 (xin)(xin) 绝 (bing)(bing) 伦 (kuang)(kuang) 的建图了:
首先把每个点拆为入点和出点,并连一条流量(可经过次数)为 1,费用(节点数)为 1 的边,起点、终点要单独拆,流量设为 2 。
而对于输入的边 (x,y),要把 x的出点与 y的入点相连(其实是个很简单的道理,一开始死活想不明白,而大佬们的题解基本都没讲,可能是觉得太简单了吧,我太蒻了。。。)。
【求答案】
答案分三种情况:
(1).当最大流等于 2 时,最大花费减 2 即为可经过的最大节点数,减 2 是因为起点、终点都经过了两次。
(2).当处于上述 1直通 n 的情况时,答案为 2,路径为:起点→终点→起点。
(3). 其他情况均为无解。
另外,情况 (1) 中输出路径时写两个 dfs 遍历满流边:
第一次随便跑,记录下跑过的节点编号,要注意的是这时候只能跑出一条路,所以找到路后要立马 break。
第二次跳过这些节点找剩下那条路径,由于最大流(所找到的路径数)最多为 2,所以这里是否 break 都无所谓(可能会快一丢丢吧)。
AC代码:
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 600005; const int inf = 0x3f3f3f3f; struct edge { int f, t, nxt; ll flow,w; }e[maxn * 2]; int hd[maxn], tot = 1; void add(int f, int t, ll flow,ll w) { e[++tot] = { f,t,hd[f],flow ,w}; hd[f] = tot; e[++tot] = { t,f,hd[t],0,-w}; hd[t] = tot; } int s, t; int n, m; int dis[maxn], pre[maxn]; ll maxflow, mincost; ll cyf[maxn]; bool inque[maxn]; bool spfa() { for (int i = 1; i <= 2*n; i++) { dis[i] = -inf; inque[i] = 0; } dis[s] = 0, inque[s] = 1, cyf[s] = inf; queue<int>q; q.push(s); while (!q.empty()) { int u = q.front(); q.pop(); inque[u] = 0; for (int i = hd[u]; i; i = e[i].nxt) { int v = e[i].t; if (e[i].flow > 0 && dis[u] + e[i].w > dis[v]) { pre[v] = i; cyf[v] = min(cyf[u], e[i].flow); dis[v] = dis[u] + e[i].w; if (!inque[v]) { inque[v] = 1; q.push(v); } } } } return dis[t] != -inf; } void mcmf() { while (spfa()) { int x = t; maxflow += cyf[t]; mincost += dis[t] * cyf[t]; while (x != s) { int i = pre[x]; e[i].flow -= cyf[t]; e[i ^ 1].flow += cyf[t]; x = e[i].f; } } } string str[105]; map<string, int>M; bool vis[maxn]; string a, b; void dfs1(int u) { vis[u-n] = 1; printf("%s\n", str[u-n].c_str()); for (int i = hd[u]; i; i = e[i].nxt) { int v = e[i].t; if (v<=n && !e[i].flow) { dfs1(v + n); break; } } } void dfs2(int u) { vis[u - n] = 1; for (int i = hd[u]; i; i = e[i].nxt) { int v = e[i].t; if (v <= n && !vis[v] && !e[i].flow) { dfs2(v + n); break; } } printf("%s\n", str[u-n].c_str()); } int main() { //freopen("test.txt", "r", stdin); scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) { cin >> str[i]; M[str[i]] = i; if (i != 1 && i != n)add(i, i + n, 1, 1);//经过点费用为1 else add(i, i + n, 2, 1);//起点和终点的容量设置为2 } int flg = 0; for (int i = 1; i <= m; i++) { cin >> a >> b; int x = M[a], y = M[b]; if (x > y)swap(x, y);//只从左向右连 flg|= (x == 1&&y == n);//判断是否起点和终点直接有边相连,这种可能是可以的。 add(x + n, y, 1, 0);//两点连边费用为0 } s = 1, t = 2*n; mcmf();//最大花费最大流 if (maxflow==2) { printf("%lld\n", mincost - 2); } else if(flg&&maxflow==1){ printf("2\n"); printf("%s\n", str[1].c_str()); printf("%s\n", str[n].c_str()); printf("%s\n", str[1].c_str()); return 0; } else { printf("No Solution!\n"); return 0; } dfs1(1 + n);//先找一条路径,并标记路径上的点 dfs2(1 + n);//再找一条 return 0; }
标签:最大,int,路径,路线,maxn,str,P2770,起点,dis 来源: https://www.cnblogs.com/MYMYACMer/p/14655425.html