1097: [POI2007]旅游景点atr(dijkstra+状压dp)

Description

FGD想从成都去上海旅游。在旅途中他希望经过一些城市并在那里欣赏风景,品尝风味小吃或者做其他的有趣的事情。经过这些城市的顺序不是完全随意的,比如说FGD不希望在刚吃过一顿大餐之后立刻去下一个城市登山,而是希望去另外什么地方喝下午茶。幸运的是,FGD的旅程不是既定的,他可以在某些旅行方案之间进行选择。由于FGD非常讨厌乘车的颠簸,他希望在满足他的要求的情况下,旅行的距离尽量短,这样他就有足够的精力来欣赏风景或者是泡MM了_.整个城市交通网络包含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需要的。

Input

第一行包含3个整数N(2<=N<=20000),M(1<=M<=200000),K(0<=K<=20),意义如上所述。

Output

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


题解:

题意让我们找一条经过标号为2到k+1的城市并且符合顺序的1到N的最短路。然后分解题意,我们可以先预处理出1…k+1到其他所有点的最短路,就可以得到1到k+1里每两点之间的最短距离,并且得到了与点N的最短距离。然后考虑符合顺序,这个数据k很小,并且题意也有暗示2到k+1这些是连续的城市,所以我们考虑状压dp完成最后的顺序问题。

考虑 dp[u][S] 表示当前在u点,我们已经经过的城市的集合为S

考虑有前提条件城市,将前提条件加入sta[j]表示到达j节点必须满足的条件,如果有前提城市,我们将其加入状态 sta[j] |= (1<<(u-2)) ,判断是否经过了这些前提城市,我们只需要将sta[j]与该状态&运算即可。

最后扫一遍 dp[i][(1<<k)-1] + dis[i][n] 取min即为答案。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MAXN = 2e5+50;
const int MAXM = 2e4+50;
const int INF = 0x3f3f3f3f;
inline int read(){
    int x=0,f=1; char ch=getchar();
    while(!isdigit(ch)){ if(ch=='-') ch=-1; ch = getchar(); }
    while(isdigit(ch)) x=x*10+ch-48,ch=getchar();
    return x*f;
}
int tot;
struct node{
    int v,w,nxt;
}edge[MAXN<<1];
int head[MAXN];
int dis[25][MAXM],sta[25];
int dp[25][(1<<20)+5];
void add_edge(int u,int v,int w){
    edge[++tot].v = v;
    edge[tot].w = w;
    edge[tot].nxt = head[u];
    head[u] = tot;
}
bool vis[MAXN];
void dijkstra(int s){
    memset(vis,false,sizeof(vis));
    priority_queue<pair<int,int> > que;
    dis[s][s] = 0;
    que.push(make_pair(0,s));
    while(!que.empty()){
        int u = que.top().second;
        que.pop();
        if(vis[u]) continue;
        vis[u] = true;
        for(int i=head[u];i;i=edge[i].nxt){
            int v = edge[i].v,w = edge[i].w;
            if(dis[s][v] > dis[s][u] + w){
                dis[s][v] = dis[s][u] + w;
                que.push(make_pair(-dis[s][v],v));
            }
        }
    }
}
int main(){
    int n=read(),m=read(),k=read();
    while(m--){
        int u=read(),v=read(),w=read();
        add_edge(u,v,w),add_edge(v,u,w);
    }
    memset(dis,INF,sizeof(dis));
    for(int i=1;i<=k+1;i++) dijkstra(i);
    int Q = read();
    while(Q--){
        int u = read(),v = read();
        sta[v] |= (1<<(u-2));
    }
    memset(dp,INF,sizeof(dp));
    dp[1][0] = 0;
    for(int s=0;s<(1<<k);s++)
        for(int i=1;i<=k+1;i++)
            if(dp[i][s]!=INF)
                for(int j=2;j<=k+1;j++)
                    if(j!=i && (sta[j]&s)==sta[j])
                        if(dp[j][s|(1<<(j-2))] > dp[i][s] + dis[i][j])
                            dp[j][s|(1<<(j-2))] = dp[i][s] + dis[i][j];
    int res = INF;
    for(int i=1;i<=k+1;i++)
    	res = min(res,dp[i][(1<<k)-1] + dis[i][n]);
    printf("%d\n",res);
    return 0;
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值