[POI2007] [LUOGU P3451] [状压DP] 旅游景点 Tourist Attractions

本题解由于作者太菜在POI及LUOGU上会TLE,该题解主要讲思路,剩下的内存优化请各位大佬自行补充,欢迎评论区讨论
本题解运行时间10406 ms,空间194584 KiB

题目描述

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),意义如上所述。以下M行,每行包含3个整数X,y,z,(1<=x,y<=n,0<z<=1000);

接下来一行,包含一个整数q,表示有q个限制条件(0<=q<n)。以下q行,每行两个整数f,l(1<=l,f<=n),表示在f停留的时候要在l之前。

输出格式

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

样例

样例输入

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

调了4天,经历了87次失败才调出来,但内存很大,只能在HZOI上过,原题80分。。。还是太菜了

记录一下这历史性的一刻;

image
image
image
image

题解

首先,当k == 0时,很容易想到跑一遍单源最短路,输出即可;

若k != 0呢;

注意到k <= 20,结合本题的一堆限制条件,很容易想到DP的解法(因为停在一个点必须在上一个点停过的限制很容易想到递推);

注意,停留不是经过!!!

于是,定义 f[i][j] 表示现在停在第i个点,以前停的点的状态为j时,从1走到i的最短路径,j中1代表停过,0代表没停过(注意这里的j包含i这个点);

这里采用状压的写法,因为需要判断停在第i个点时,必须在第i个点前停的点是否全部停过;

初始化

很容易发现,第1个点是必须停的,所以j == 0不合法,状态从j == 1开始转移;

当现在停在第i个点,以前只停在第i个点和第1个点时,将 f[i][j] 赋值为1到i的最短路;

当现在停在第1个点,以前只停在第i个点和第1个点时,将 f[i][j] 赋值为1到i的最短路 * 2;

剩下的初始化为极大值即可(因为要找最小值);

状态转移方程

\[f[i][j] = min(f[i][j], f[k][o] + d[k][i]) \]

其中,i是j中停过的点(除了1),o为j去掉i后的状态,k是o中停过的点(除了1),d[k][i]为从k到i的最短路;

方程很好理解,但有一些细节需要注意;

  1. 需要先枚举状态,再从状态中找点;
  2. 枚举状态时,需要判断此状态是否合法,这里我在预处理时处理出来了;
  3. 位运算注意优先级;

预处理

处理出:

  1. 前k + 1个点的每两个点的最短路(建议用堆优化的dij);
  2. 所有的合法状态;

代码

#include <iostream>
#include <cstring>
#include <queue>
#include <cmath>
#include <algorithm>
using namespace std;
int n, m, k;
int q;
int f[22][(1 << 21)];
int s[20001];
int g[1048576]; //当k == 20时,ccc == 1048575,故这里开1048576;
int gg[1048576]; //同理;
int ccc;
struct sss{
	int t, ne, w;
}e[400001];
int h[400001], cnt;
void add(int u, int v, int ww) {
	e[++cnt].w = ww;
	e[cnt].t = v;
	e[cnt].ne = h[u];
	h[u] = cnt;
}
int dis[20001];
bool vis[20001];
typedef pair<int, int> pii;
void dij(int x) { //最短路;
	memset(dis, 0x3f, sizeof(dis));
	memset(vis, 0, sizeof(vis));
	priority_queue<pii, vector<pii>, greater<pii> > q;
	q.push({0, x});
	dis[x] = 0;
	while(!q.empty()) {
		int t = q.top().second;
		q.pop();
		if (vis[t]) continue;
		vis[t] = true;
		for (int i = h[t]; i; i = e[i].ne) {
			int u = e[i].t;
			if (dis[u] > dis[t] + e[i].w) {
				dis[u] = dis[t] + e[i].w;
				q.push({dis[u], u});
			}
		}
	}
}
int d[22][22];
void w() {
	memset(f, 0x3f, sizeof(f));
	for (int i = 2; i <= k + 1; i++) {
		f[i][(1 << (i - 1)) + 1] = d[1][i];
		f[1][(1 << (i - 1)) + 1] = 2 * d[1][i];
	}
	for (int j = 1; j <= ccc; j++) { //枚举所有合法状态;
		for (int i = 2; i <= k + 1; i++) {
			if (((1 << (i - 1)) | g[j]) == g[j]) {
				int o = g[j] - (1 << (i - 1));
				int k1 = lower_bound(gg + 1, gg + 1 + ccc, o) - gg; //判断o是否合法;
				if (k1 > ccc || gg[k1] != o) continue;
				for (int k1 = 2; k1 <= k + 1; k1++) {
					if (((1 << (k1 - 1)) | o) == o) {
						f[i][g[j]] = min(f[i][g[j]], f[k1][o] + d[k1][i]);
					}
				}
			}
		}
	}
}
int main() {
	cin >> n >> m >> k;
	int a, b, c;
	ccc = 0;
	for (int i = 1; i <= m; i++) {
		cin >> a >> b >> c;
		add(a, b, c);
		add(b, a, c);
	}
	if (k == 0) {
		dij(1);
		cout << dis[n];
		return 0;
	}
	for (int i = 1; i <= k + 1; i++) {
		dij(i);
		d[i][k + 2] = dis[n];
		d[k + 2][i] = dis[n];
		for (int j = 1; j <= k + 1; j++) {
			d[i][j] = dis[j];
			d[j][i] = dis[j];
		}
	}
	cin >> q;
	int a1, b1;
	for (int i = 1; i <= q; i++) {
		cin >> a1 >> b1;
		s[b1] |= (1 << (a1 - 1));
	}
	for (int i = 2; i < (1 << (k + 1)); i++) {
		bool v = true;
		if ((i & 1) == 0) continue;
		for (int j = 1; j <= k + 1; j++) {
			if (((1 << (j - 1)) | i) == i) {
				if ((i & s[j]) != s[j]) {
					v = false;
					break;
				}
			}
		}
		if (v) {
			g[++ccc] = i; //存储所有合法状态;
		}
	}
	memcpy(gg, g, sizeof(g));
	sort(gg + 1, gg + 1 + ccc); //后面要用二分查找,所以要排序;
	w();
	int ans = 99999999999;
	for (int i = 1; i <= k + 1; i++) {
		ans = min(ans, f[i][(1 << (k + 1)) - 1] + d[i][k + 2]); //找最小值并输出(f[i][(1 << (k + 1)) - 1] 保证题目要求的点都经过);
	}
	cout << ans;
	return 0;
}
posted @ 2024-04-07 20:53  Peppa_Even_Pig  阅读(7)  评论(0编辑  收藏  举报