【BJOJ1097】旅游景点 atr (spfa+状压dp)

题目描述

FGD想从成都去上海旅游。由于FGD非常讨厌乘车的颠簸,他希望在满足他的要求的情况下,旅行的距离尽量短,这样他就有足够

的精力来欣赏风景。

整个城市交通网络包含N个城市以及城市与城市之间的双向道路M条。城市自1至N依次编号,道路亦然。没有从某个城市直接到它

自己的道路,两个城市之间最多只有一条道路直接相连,但可以有多条连接两个城市的路径。任意两条道路如果相遇,则相遇点也

必然是这N个城市之一,在中途,由于修建了立交桥和下穿隧道,道路是不会相交的。每条道路都有一个固定长度。在中途,FGD

想要经过K(K<=N-2)个城市。成都编号为1,上海编号为N,而FGD想要经过的N个城市编号依次为2,3,…,K+1. 举例来说,假设交通网

络如下图。FGD想要经过城市2,3,4,5,并且在2停留的时候在3之前,而在4,5停留的时候在3之后。那么最短的旅行方案是

1-2-4-3-4-5-8,总长度为19。注意FGD为了从城市2到城市4可以不路过城市3,但不在城市3停留。这样就不违反FGD的要求了。并且

由于FGD想要走最短的路径,因此这个方案正是FGD需要的。

输入

第1行包含3个整数N(2< =N< =20000), M(1<=M<=200000),K(0< =K<=20)

第2..M+1行,每行包含3个整数X,Y,Z,(1 ≤ x < y ≤N, 1 ≤z ≤ 1000).表示城市x到城市y距离为z,数据保证必然存在一条路径从城市

1到城市n, 经过所有城市。

第M+2行,一个整数g, 0 ≤ g ≤k·(k−1)/2。表示FGD停留的限制条件有g个。

第M+3..M+2+g行,包含2个整数ri和si ,2 ≤ ri ≤ k +1, 2 ≤ si ≤ k +1, ri不等于si。FGD访问城市si之前,必须访问ri。

输出

只包含一行,包含一个整数,表示最短的旅行距离

思路

分析发现k个点的先后到达顺序影响答案,其他点无论何时访问对于答案的贡献是固定的。因此可以先把研究范围缩小到这k个点

中。鉴于k的大小比较友善,可以考虑状压这k个点的状态(已访问/未访问)。

先spfa预处理出这k个点到每个点的最短距离,设f[i][j]表示状态i下最后到达第j个点的最小最短路程,不难得出转移式:

f[s][i]=min(f[s'][j]+dis[i][j])

对于访问某些点之前必须先访问其他点的限制条件,不妨同样通过状压的方式存下访问点i之前必须访问的点,在在转移的时候跳过非法状态即可。

#include <queue>
#include <cstdio>
#include <iostream>
 
using namespace std;
 
const int MAXN=20010;
const int MAXM=200010;
const int MAXK=22;
const int INF=1e9;
 
int n,m,k,g;
bool inq[MAXN];
 
int head[MAXN],tot,lim[MAXK],f[1<<20][MAXK],dis[MAXK][MAXN],ans=INF;
struct edge{int to,nxt,c;}e[MAXM<<1];
 
int getint(){
    int v=0; char ch;
    while(!isdigit(ch=getchar())); v=ch-48;
    while(isdigit(ch=getchar())) v=v*10+ch-48; return v;
}
 
void insert(int u,int v,int cost){
    e[++tot].to=v; e[tot].nxt=head[u]; head[u]=tot; e[tot].c=cost;
    e[++tot].to=u; e[tot].nxt=head[v]; head[v]=tot; e[tot].c=cost;
}
 
void spfa(int S){
    for(int i=1;i<=n;i++) dis[S][i]=INF; dis[S][S]=0;
    queue<int> Q; Q.push(S); inq[S]=true;
    while(!Q.empty()){
        int now=Q.front(); Q.pop();
        for(int i=head[now];i;i=e[i].nxt){
            if(dis[S][e[i].to]>dis[S][now]+e[i].c){
                dis[S][e[i].to]=dis[S][now]+e[i].c;
                if(!inq[e[i].to]){
                    Q.push(e[i].to);
                    inq[e[i].to]=true;
                }
            }
        }
        inq[now]=false;
    }
}
 
int main(){
    n=getint(); m=getint(); k=getint();
    for(int i=1;i<=m;i++){
        int a=getint(),b=getint(),c=getint();
        insert(a,b,c);
    }
    g=getint();
    for(int i=1;i<=g;i++){
        int a=getint(),b=getint();
        lim[b]|=(1<<(a-2));
    }
    for(int i=1;i<=k+1;i++) spfa(i);
    for(int i=0;i<(1<<k);i++)
        for(int j=1;j<=k+1;j++)
            f[i][j]=INF;
    for(int i=1;i<=k+1;i++)
        if(!lim[i])
            f[1<<(i-2)][i]=dis[1][i];
    for(int i=0;i<(1<<k);i++){
        for(int j=1;j<=k+1;j++){
            if(f[i][j]==INF) continue;
            for(int l=1;l<=k+1;l++){
                if((lim[l]&i)!=lim[l]) continue;
                f[i|(1<<(l-2))][l]=min(f[i|(1<<(l-2))][l],f[i][j]+dis[l][j]);
            }
        }
    }
    for(int i=1;i<=k+1;i++)
        ans=min(ans,f[(1<<k)-1][i]+dis[i][n]);
    printf("%d\n",ans);
    return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值