【网络流24题之航空线路】(只求方案数)xoj1248

题目描述

给定一张航空图,图中顶点代表城市,边代表2城市间的直通航线。现要求找出一条满 足下述限制条件的且途经城市最多的旅行路线。

(1)从最西端城市出发,单向从西向东途经若干城市到达最东端城市,然后再单向从东 向西飞回起点(可途经若干城市)。

(2)除起点城市外,任何城市只能访问1次。

对于给定的航空图,试设计一个算法找出一条满足要求的最佳航空旅行路线。

输入

由文件input.txt提供输入数据。文件第1 行有2个正整数N 和V,N 表示城市数,N<100, V 表示直飞航线数。接下来的N行中每一行是一个城市名,可乘飞机访问这些城市。城市名 出现的顺序是从西向东。也就是说,设i,j 是城市表列中城市出现的顺序,当i>j 时,表示 城市i 在城市j 的东边,而且不会有2 个城市在同一条经线上。城市名是一个长度不超过 15 的字符串,串中的字符可以是字母或阿拉伯数字。例如,AGR34或BEL4。

再接下来的V 行中,每行有2 个城市名,中间用空格隔开,如 city1 city2 表示city1 到city2 有一条直通航线,从city2 到city1 也有一条直通航线。

输出

程序运行结束时,将最佳航空旅行路线输出到文件output.txt 中。

文件第1 行是旅行路 线中所访问的城市总数M。

// 接下来的M+1 行是旅行路线的城市名,每行写1 个城市名。

// 首先是出发城市名,然后按访问顺序列出其它城市名。

//注意,最后1行(终点城市)的城市名 必然是出发城市名。

如果问题无解,则输出“No Solution!”。

样例输入

8 9
Vancouver
Yellowknife
Edmonton
Calgary
Winnipeg
Toronto
Montreal
Halifax
Vancouver Edmonton
Vancouver Calgary
Calgary Winnipeg
Winnipeg Toronto
Toronto Halifax
Montreal Halifax
Edmonton Montreal
Edmonton Yellowknife
Edmonton Calgary
样例输出

7

题解

  • 根据题意,首先我们要找出题目的限制。就是每个城市做多只能经过一次(起点可以经过两次,我们把终点也看成可以经过两次)。可以用容量为1(两次为2)的边来限制,题目又要求经过最多城市,我们可以求一个最大费用流(插入费用时变负数,按照最小费用的过程来做,答案再取反)。建模比较容易,只需要将每个点拆成两个点,一个代表进入城市,一个代表离开城市,显然,城市之间的边容量可以为inf,花费为0(已经有S->ii->T的边限制)。
    具体代码
#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
#include <map>
#define _(d) while(d (((ch=getchar())>47)&&ch<58))
using namespace std;
const int MaxN=223,Maxe=MaxN*MaxN/2;
const int inf=0x7fffffff;
int N,V,ans,h[MaxN],pre[MaxN],vst[MaxN],d[MaxN];
int S,T,tt,from[Maxe],to[Maxe],flow[Maxe],cst[Maxe],nxt[Maxe];
string str[MaxN];
map<string,int> hash;
inline void Get(int &x){char ch;_(!);x=ch-48;_()x=(x<<3)+(x<<1)+ch-48;}
inline void addedge(int u,int v,int w,int cc)
{
    from[tt]=u;to[tt]=v;flow[tt]=w;cst[tt]=cc;
    nxt[tt]=h[u];h[u]=tt++; 
}
inline void add(int u,int v,int w,int cc)
{
    addedge(u,v,w,cc);addedge(v,u,0,-cc);   
}
queue<int> q;
bool spfa()
{
    for(int i=0;i<MaxN;i++) pre[i]=-1,vst[i]=0,d[i]=inf;
    q.push(S);vst[S]=1;d[S]=0;
    while(!q.empty())
    {
        int u=q.front();q.pop();
        vst[u]=0;
        for(int i=h[u];i!=-1;i=nxt[i])
        {
            int v=to[i];
            if(flow[i]&&d[v]>d[u]+cst[i])
            {
                d[v]=d[u]+cst[i];
                pre[v]=i;
                if(!vst[v])
                {
                    q.push(v);
                    vst[v]=1;   
                }   
            }   
        }
    }
    return d[T]!=inf;
}
inline void work()
{
    int ft;
    while(spfa())
    {
        ft=inf;
        for(int i=T;i!=S;i=from[pre[i]]){ft=min(ft,flow[pre[i]]);}
        for(int i=T;i!=S;i=from[pre[i]])
        {
            int k=pre[i];
            flow[k]-=ft;
            flow[k^1]+=ft;
            ans+=cst[k]*ft; 
        }
    }
    return;
}
int main()
{
    for(int i=0;i<MaxN;i++) h[i]=-1;
    string str1,str2;
    Get(N);Get(V);
    S=1;T=2*N;
    add(S,S+N,2,-1);add(N,T,2,-1);
    for(int i=1;i<=N;i++)
    {
        cin>>str[i];
        hash[str[i]]=i;
        if(i>1&&i<N) add(i,i+N,1,-1);
    }
    for(int i=1;i<=V;i++)
    {
        cin>>str1>>str2;
        int a=hash[str1],b=hash[str2];
        if(a>b) swap(a,b);
        add(a+N,b,inf,0);   
    }
    work();
    if(flow[0]) printf("No Solution!\n");
    else printf("%d\n",-2-ans);
    return 0;   
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值