[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分。。。还是太菜了;
记录一下这历史性的一刻;
题解
首先,当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;
剩下的初始化为极大值即可(因为要找最小值);
状态转移方程
其中,i是j中停过的点(除了1),o为j去掉i后的状态,k是o中停过的点(除了1),d[k][i]为从k到i的最短路;
方程很好理解,但有一些细节需要注意;
- 需要先枚举状态,再从状态中找点;
- 枚举状态时,需要判断此状态是否合法,这里我在预处理时处理出来了;
- 位运算注意优先级;
预处理
处理出:
- 前k + 1个点的每两个点的最短路(建议用堆优化的dij);
- 所有的合法状态;
代码
#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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· 使用C#创建一个MCP客户端
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· Windows编程----内核对象竟然如此简单?