题解 P2770 【航空路线问题】

· · 题解

思路:

这道题求出答案应该不难,

对于每个点拆成Ai和Bi两个点,两个点之间的容量为1,边权为1,(因为每个点只能选一次,每选一个点可以对答案造成1的贡献) (若i为1或n容量应为2,因为这两个点可以选两次)

对于每条从u到v的边,让Bu向Av建一条容量为1,边权为0的边(因为每条边只能选一次,选边并不会对答案造成影响)

最后从源点向A1建一条容量为2,边权为0,的边

从Bn向汇点建一条容量为2,边权为0的边

最大费用最大流即可

问题是如何输出城市呢?

我的思路是进行两遍dfs,

第一次dfs找到一条1到n所有边的flow都为0的路径正序输出,

第二次dfs找到另一条1到n所有边的flow都为0的路径倒序输出(这次n不输出)

还是有一定坑点的,具体看代码吧

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<cmath>
#include<queue>
#include<map>
#define inf 0x7fffffff/2
#define eps 1e-6
#define N 100010
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
inline ll read()
{
    char ch=getchar();
    ll s=0,w=1;
    while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){s=s*10+ch-'0';ch=getchar();}
    return s*w;
}
int n,m;
map<string,int>ma;
struct edge
{
    int next,to,fl,v;
}e[N<<1];
int head[N],cnt=1;
int dist[N],pre[N],vis[N],minn[N];
queue<int>Q;
int s,t,val,flag[N],check;
string ss[N];
inline void add_edge(int from,int to,int fl,int v){e[++cnt].to=to;e[cnt].next=head[from];e[cnt].fl=fl;e[cnt].v=v;head[from]=cnt;}
void update(int x,int flow)
{
    e[pre[x]].fl-=flow;
    e[pre[x]^1].fl+=flow;
    if(e[pre[x]^1].to)update(e[pre[x]^1].to,flow);
}
inline int spfa()
{
    memset(vis,0,sizeof(vis));while(!Q.empty())Q.pop();
    for(register int i=1;i<=t;i++)dist[i]=-inf;
    minn[s]=inf;dist[s]=0;Q.push(s);vis[s]=1;
    while(!Q.empty())
    {
        int x=Q.front();Q.pop();vis[x]=0;
        for(register int i=head[x];i;i=e[i].next)
        {
            if(dist[e[i].to]<dist[x]+e[i].v&&e[i].fl)
            {
                dist[e[i].to]=dist[x]+e[i].v;
                pre[e[i].to]=i;
                minn[e[i].to]=min(minn[x],e[i].fl);
                if(!vis[e[i].to])
                {
                    vis[e[i].to]=1;
                    Q.push(e[i].to);
                }
            }
        }
    }
    if(dist[t]==-inf)return -inf;
    update(t,minn[t]);val+=minn[t];
    return minn[t]*dist[t];
}
inline int EK()
{
    int sum=0;
    while(1)
    {
        int x=spfa();
        if(x==-inf)return sum;
        sum+=x;
    }
}//最大费用最大流
void dfs1(int x)
{
    cout<<ss[x]<<endl;//第一遍dfs正序输出
    vis[x]=1;//不让第二次dfs再找到这个点
    for(register int i=head[x];i;i=e[i].next)
    {
        if(e[i].to>n&&e[i].to<=2*n&&e[i].fl==0){dfs1(e[i].to-n);break;}//第一次dfs只找一条路径,找到就break
    }
}
void dfs2(int x)
{
    vis[x]=1;
    for(register int i=head[x];i;i=e[i].next)
    {
        if(e[i].to>n&&e[i].to<=2*n&&e[i].fl==0&&!vis[e[i].to-n]){dfs2(e[i].to-n);}//不走第一次路径走过的点
    }
    cout<<ss[x]<<endl;//第二次dfs倒序输出
}//vis[n]在第一次dfs已经设为1,不会输出第二次
int main()
{
    n=read(),m=read();t=n*2+1;
    for(register int i=1;i<=n;i++)
    {
        cin>>ss[i];ma[ss[i]]=i;
    }
    for(register int i=1;i<=m;i++)
    {
        string s1,s2;
        cin>>s1>>s2;
        int x=ma[s1],y=ma[s2];
        if(x>y)swap(x,y);//让西边的点向东边建边
        if(x==1&&y==n)check=1;//如果有直接1-n的路径
        add_edge(x,y+n,1,0);add_edge(y+n,x,0,0);
    }
    add_edge(s,n+1,inf,0);add_edge(n+1,s,0,0);
    add_edge(n,t,inf,0);add_edge(t,n,0,0);
    for(register int i=1;i<=n;i++)
    {
        if(i!=1&&i!=n)add_edge(i+n,i,1,1),add_edge(i,i+n,0,-1);
        else add_edge(i+n,i,2,1),add_edge(i,i+n,0,-1);
    }
    //对于我的代码,1-n是Bi,n+1-2*n是Ai
    int maxflow=EK();
    if(val==2)printf("%d\n",maxflow-2);//1和n被重复计算,应该减去
    else if(val==1&&check){printf("%d\n",2);cout<<ss[1]<<endl<<ss[n]<<endl<<ss[1]<<endl;return 0;}//如果有1-n直接的路径即使只有1条也是满足条件的
    else {printf("No Solution!\n");return 0;}
    memset(vis,0,sizeof(vis));
    dfs1(1);dfs2(1);//输出城市
    return 0;
}

如果认为我这篇题解对你有帮助的可以给我点一下赞qwq。如果有任何疑问,或者认为我的题解有什么问题的话,请务必私信我,感激不尽!我会努力把我的题解写得最好的!