洛谷P1433 吃奶酪 题解 状态压缩DP
题目链接:https://www.luogu.com.cn/problem/P1433
题目大意
房间里放着 \(n\) 块奶酪。一只小老鼠要把它们都吃掉,问至少要跑多少距离?老鼠一开始在 \((0,0)\) 点处。
输入格式
第一行一个正整数 \(n\)。
接下来每行 \(2\) 个实数,表示第 \(i\) 块奶酪的坐标。
两点之间的距离公式为 \(\sqrt{(x_1-x_2)^2+(y_1-y_2)^2}\)。
输出格式
一个数,表示要跑的最少距离,保留 \(2\) 位小数。
解题思路
定义状态 \(f[st][i]\) 表示当前状态为 \(st\) ,且最后一个到达的点是 \(i\) 点时的最少距离。
首先,因为 \(st\) 的二进制表示中的那些为 \(1\) 的位表示的是小老鼠已经到达的点,所以如果 \(st\) 的第 \(i\) 位不为 \(1\),则状态 \(f[st][i]\) 不合法。
其次:
如果状态 \(st\) 有且只有一位为 \(1\) (即 __builtin_popcount(st) == 1),并且我们假设为 \(1\) 的这一位为第 \(i\) 位,则 \(f[st][i] = \sqrt{x_i^2 + y_i^2}\) (因为小老鼠一开始在 \((0,0)\) 点,从 \((0,0)\) 点到 \((x_i,y_i)\) 点的距离是 \(\sqrt{x_i^2 + y_i^2}\));
否则(状态 \(st\) 为 \(1\) 的位数 \(\gt 1\)),说明状态 \(f[st][i]\) 是可以通过一个合法的状态 \(f[st2][j]\) 转换过来的。(其中 st2 = st^(1<<i))
此时,我们可以得到状态转移方程为:
\]
其中,st2 = st^(1<<i)。
而 \(\sqrt{(x_i-x_j)^2+(y_i-y_j)^2}\) 表示的就是点 \((x_j,y_j)\) 到点 \((x_i,y_i)\) 的距离。
实现代码如下:
#include <bits/stdc++.h>
using namespace std;
double dis(double x1, double y1, double x2, double y2) {
return sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2));
}
int n;
double x[15], y[15], f[(1<<15)][15];
bool vis[(1<<15)][15];
int main() {
cin >> n;
for (int i = 0; i < n; i ++) cin >> x[i] >> y[i];
for (int st = 0; st < (1<<n); st ++) {
for (int i = 0; i < n; i ++) {
if (!(st & (1<<i))) continue;
if (__builtin_popcount(st) == 1) f[st][i] = dis(0, 0, x[i], y[i]);
else {
int st2 = st ^ (1<<i);
for (int j = 0; j < n; j ++) {
if (!(st2 & (1<<j))) continue;
double tmp = f[st2][j] + dis(x[i], y[i], x[j], y[j]);
if (!vis[st][i] || f[st][i] > tmp) {
vis[st][i] = true;
f[st][i] = tmp;
}
}
}
}
}
double ans = f[(1<<n)-1][0];
for (int i = 1; i < n; i ++) ans = min(ans, f[(1<<n)-1][i]);
printf("%.2lf\n", ans);
return 0;
}
代码分析
我们对这个代码中的主要片段进行一下分析:
double dis(double x1, double y1, double x2, double y2) {
return sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2));
}
dis函数用于计算点 \((x_1,y_1)\) 到点 \((x_2,y_2)\) 之间的距离。
int n;
double x[15], y[15], f[(1<<15)][15];
bool vis[(1<<15)][15];
n用来表示点(或者说——奶酪)的数量。
\(x[i],y[i]\) 用于表示点的距离。
\(f[st][i]\) 的含义我们已经讲过了,这里就不再继续讲了。
\(vis[st][i]\) 相当于我们记忆化的操作。
我们以往的操作都会选择将 \(f[st][i]\) 赋为一家很大的值,或者将它赋值为-1来表示无穷大,但是我们开一个vis数组,通过 \(vis[st][i]\) 是否为 \(true\) 来判断状态 \(f[st][i]\) 有没有更新过也是可以的(没有更新过说明 \(f[st][i]\) 对应的状态还是无穷大,更新过说明 \(f[st][i]\) 已经被更新为了一个较小的值)。
这部分逻辑在我们代码中 \(f[st2][j]\) 更新 \(f[st][i]\) 的时候有遇到:
double tmp = f[st2][j] + dis(x[i], y[i], x[j], y[j]);
if (!vis[st][i] || f[st][i] > tmp) {
vis[st][i] = true;
f[st][i] = tmp;
}
if (__builtin_popcount(st) == 1) f[st][i] = dis(0, 0, x[i], y[i]);
这句话对应我们上面分析的第一种情况(如果状态 \(st\) 有且只有一位为 \(1\)),此时就直接更新 \(f[st][i]\) 为起点(\((0,0)\)) 到点 \(i\)(\((x_i, y_i)\)) 的距离即可。
否则,对于状态 \(f[st][i]\) ,需要找到所有它的前一步的状态 \(f[st2][j]\),并且通过如下代码求得 \(f[st][i]\):
int st2 = st ^ (1<<i);
for (int j = 0; j < n; j ++) {
if (!(st2 & (1<<j))) continue;
double tmp = f[st2][j] + dis(x[i], y[i], x[j], y[j]);
if (!vis[st][i] || f[st][i] > tmp) {
vis[st][i] = true;
f[st][i] = tmp;
}
}
而最终的状态 \(st\) 肯定等于 \(2^n-1\)(\(2^n-1\) 的后 \(n\) 位都为 \(1\),表示 \(n\) 个点都走过),所以答案即为
\]
我们是通过如下代码段来获得答案的:
double ans = f[(1<<n)-1][0];
for (int i = 1; i < n; i ++) ans = min(ans, f[(1<<n)-1][i]);
printf("%.2lf\n", ans);
最后,也不要忘了输出我们的 ans,同时保留2位小数哦。
最后的最后:
关于数位DP,最好还是按照坐标从 \(0\) 到 \(n-1\) 为好,因为这样的 \(i\) 刚好能跟状态在 \([0, 2^n-1]\) 范围内的数字一一对应。所以希望还是能够按照坐标从 \(0\) 开始比较好。
洛谷P1433 吃奶酪 题解 状态压缩DP的更多相关文章
- 洛谷 P1433 吃奶酪 Label:dfs && 剪枝Ex
题目描述 房间里放着n块奶酪.一只小老鼠要把它们都吃掉,问至少要跑多少距离?老鼠一开始在(0,0)点处. 输入输出格式 输入格式: 第一行一个数n (n<=15) 接下来每行2个实数,表示第i块 ...
- 洛谷 P1433 吃奶酪【DFS】+剪枝
题目链接:https://www.luogu.org/problemnew/show/P1433 题目描述 房间里放着n块奶酪.一只小老鼠要把它们都吃掉,问至少要跑多少距离?老鼠一开始在(0,0)点处 ...
- 洛谷 P1433 吃奶酪 状压DP
题目描述 分析 比较简单的状压DP 我们设\(f[i][j]\)为当前的状态为\(i\)且当前所在的位置为\(j\)时走过的最小距离 因为老鼠的坐标为\((0,0)\),所以我们要预处理出\(f[1& ...
- 集训作业 洛谷P1433 吃奶酪
嗯?这题竟然是个绿题. 这个题真的不难,不要被他的难度吓到,我们只是不会计算2点之间的距离,他还给出了公式,这个就有点…… 我们直接套公式去求出需要的值,然后普通的搜索就可以了. 这个题我用的深搜,因 ...
- 洛谷P1433 吃奶酪【dfs】【剪枝】
题目:https://www.luogu.org/problemnew/show/P1433 题意: 给定n个坐标,要求从(0,0)开始走遍所有点,最少经过的路程. 思路: 刚开始想像数字三角形一样适 ...
- 洛谷 P1433 吃奶酪
题目描述 房间里放着n块奶酪.一只小老鼠要把它们都吃掉,问至少要跑多少距离?老鼠一开始在(0,0)点处. 输入输出格式 输入格式: 第一行一个数n (n<=15) 接下来每行2个实数,表示第i块 ...
- 洛谷 - P1433 - 吃奶酪 - dfs
https://www.luogu.org/problemnew/show/P1433 并不是每一个求最短距离就是bfs,这个肯定是dfs. 直接计算15!可以知道枚举必定超时,但是! 我们dfs非常 ...
- 洛谷 P1433 吃奶酪(记忆化)
题目描述 房间里放着n块奶酪.一只小老鼠要把它们都吃掉,问至少要跑多少距离?老鼠一开始在(0,0)点处. 输入输出格式 输入格式: 第一行一个数n (n<=15) 接下来每行2个实数,表示第i块 ...
- 洛谷P1433 吃奶酪
#include<iostream> #include<math.h> using namespace std ; ; int n; bool st[N]; double x[ ...
随机推荐
- TCP 3次握手 && 4次分手
原文:https://github.com/jawil/blog/issues/14 3次握手 第一次握手:建立连接.客户端发送连接请求报文段,将SYN位置为1,Sequence Number为x:然 ...
- 2_04_MSSQL课程_查询_类型转换、表联合、日期函数、字符串函数
类型转换 Convert(目标类型,转换的表达式,格式规范) Cast(表达式 as 类型) select Convert(nvarchar(32)),CustomerId))+Title from ...
- 十二、js去掉空格_比较字符长度_中英文判断_页面初始化_简体字与繁字体判断
1.去掉字符串前后所有空格 function trimBlank(str){ return str.replace(/(^\s*)|(\s*$)/g, ""); } 2.字符串长度 ...
- SystemVerilog Assertion 设计、调试、测试总结(3)
上两篇主要是讲述断言的概念,基本语法,总结等等 这一篇主要是以PPT的形式展示各个场景下关于断言的应用. 为了在设计中加入断言的功能,因此需要写一个DUT.如下: `define `define fr ...
- AJAX封装数据处理简单操作
数据的封装处理主要展现在JS中,在页面里面引入封装的JS, "js/ajax.js" 简单封装将get和post方法都写入,get的方法和post的方法依然需要严格区分,包括typ ...
- redis-Hash(哈希表)
Redis hash 是一个string类型的field和value的映射表,它的添加.删除操作都是O(1)(平均).hash特别适用于存储对象,将一个对象存储在hash类型中会占用更少的内存,并且可 ...
- 为什么Fun函数能够执行
#include<stdio.h> #include<windows.h> void Fun() { printf("Kali-Team\n"); } in ...
- 每个项目中,你必须知道的11个Java第三方类库。
Java第三方library ecosystem是一个很广阔的范畴.不久前有人撰文:每个项目中,你必须知道的11个Java第三方类库. 单元测试 1.DBUnit DBunit是一个基于junit扩展 ...
- keras人工神经网络构建入门
//2019.07.29-301.Keras 是提供一些高度可用神经网络框架的 Python API ,能帮助你快速的构建和训练自己的深度学习模型,它的后端是 TensorFlow 或者 Theano ...
- i春秋-百度杯九月场-YeserCMS(cmseasy的UpdateXML注入漏洞)
学习了大佬们的操作才做出来,记录一次菜鸡的无能为力. tips:flag在网站根目录下的flag.php中.我们的目标就是flag.php了. 题目说是心的CMS:YeserCMS,然而百度一下,出来 ...