其他分享
首页 > 其他分享> > P2770 航空路线问题(最大费用最大流)

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