【bzoj1097】[POI2007]旅游景点atr 状压dp+堆优化Dijkstra
题目描述
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需要的。
输入
第一行包含3个整数N(2<=N<=20000),M(1<=M<=200000),K(0<=K<=20),意义如上所述。
输出
只包含一行,包含一个整数,表示最短的旅行距离。
样例输入
8 15 4
1 2 3
1 3 4
1 4 4
1 6 2
1 7 3
2 3 6
2 4 2
2 5 2
3 4 3
3 6 3
3 8 6
4 5 2
4 8 6
5 7 4
5 8 6
3
2 3
3 4
3 5
样例输出
19
题解
状压dp+堆优化Dijkstra
题目给出$k\le 20$及每个点经过一次,显然状压dp。
不过由于点数非常多,因此不能以点数为状态,而是以关键点为状态。
设$f[i][j]$表示经过的点的状态为$i$,当前所在点为第$j$个关键点的最小路程。
考虑枚举下一个关键点,所走的路径一定是它们之间的最短路。
所以预处理出$1...k+1$到所有点的最短路,然后初始状态为$f[1<<i][i]=dis[1][i]$,转移时加上最短距离。
最后的答案就是$min(f[(1<<k)-1][i]+dis[i][n])$。
时间复杂度$O(k(n+m)\log n+2^k·k^2)$
#include <queue> #include <cstdio> #include <cstring> #include <utility> #define N 20010 #define M 400010 using namespace std; typedef pair<int , int> pr; priority_queue<pr> q; int head[N] , to[M] , len[M] , next[M] , cnt , dis[22][N] , vis[N] , lim[N] , f[1 << 20][21]; void add(int x , int y , int z) { to[++cnt] = y , len[cnt] = z , next[cnt] = head[x] , head[x] = cnt; } void dijkstra(int s) { int i , x; memset(dis[s] , 0x3f , sizeof(dis[s])) , memset(vis , 0 , sizeof(vis)); dis[s][s] = 0 , q.push(pr(0 , s)); while(!q.empty()) { x = q.top().second , q.pop(); if(vis[x]) continue; vis[x] = 1; for(i = head[x] ; i ; i = next[i]) if(dis[s][to[i]] > dis[s][x] + len[i]) dis[s][to[i]] = dis[s][x] + len[i] , q.push(pr(-dis[s][to[i]] , to[i])); } } int main() { int n , m , d , p , i , j , k , x , y , z , ans = 1 << 30; scanf("%d%d%d" , &n , &m , &d); for(i = 1 ; i <= m ; i ++ ) scanf("%d%d%d" , &x , &y , &z) , add(x , y , z) , add(y , x , z); for(i = 1 ; i <= d + 1 ; i ++ ) dijkstra(i); scanf("%d" , &p); for(i = 1 ; i <= p ; i ++ ) scanf("%d%d" , &x , &y) , lim[y - 1] |= (1 << (x - 2)); if(!d) { printf("%d\n" , dis[1][n]); return 0; } memset(f , 0x3f , sizeof(f)); for(i = 1 ; i <= d ; i ++ ) if(!lim[i]) f[1 << (i - 1)][i] = dis[1][i + 1]; for(i = 1 ; i < 1 << d ; i ++ ) for(j = 1 ; j <= d ; j ++ ) if(i & (1 << (j - 1))) for(k = 1 ; k <= d ; k ++ ) if(!(i & (1 << (k - 1))) && !(~i & lim[k])) f[i | (1 << (k - 1))][k] = min(f[i | (1 << (k - 1))][k] , f[i][j] + dis[j + 1][k + 1]); for(i = 1 ; i <= d ; i ++ ) ans = min(ans , f[(1 << d) - 1][i] + dis[i + 1][n]); printf("%d\n" , ans); return 0; }