洛谷 P1433. 吃奶酪 --- 状态压缩dp -- 保留x位小数的方式

吃奶酪

题目描述

房间里放着 \(n\) 块奶酪。一只小老鼠要把它们都吃掉,问至少要跑多少距离?老鼠一开始在 \((0,0)\) 点处。

输入格式

第一行有一个整数,表示奶酪的数量 \(n\)

\(2\) 到第 \((n + 1)\) 行,每行两个实数,第 \((i + 1)\) 行的实数分别表示第 \(i\) 块奶酪的横纵坐标 \(x_i, y_i\)

输出格式

输出一行一个实数,表示要跑的最少距离,保留 \(2\) 位小数。

样例 #1

样例输入 #1

4
1 1
1 -1
-1 1
-1 -1

样例输出 #1

7.41

提示

数据规模与约定

对于全部的测试点,保证 \(1\leq n\leq 15\)\(|x_i|, |y_i| \leq 200\),小数点后最多有 \(3\) 位数字。

提示

对于两个点 \((x_1,y_1)\)\((x_2, y_2)\),两点之间的距离公式为 \(\sqrt{(x_1-x_2)^2+(y_1-y_2)^2}\)


\(2022.7.13\):新增加一组 \(\text{Hack}\) 数据。


题解

发现了宝藏oiwiki 这道题需要用到的 位运算知识
求最短路也不一定用bfs啊
本来是想用dfs来做的 但是做不出来
用题解里的状压dp做吧 当复习了(虽然考试也做不出来)
状态压缩一般只能通过\(N <= 21\)的数据范围
配合 蒙德里安的梦想洛谷题解 一起食用
定义一个f[i][j] ,表示老鼠走到第i个奶酪时,且走过的二进制数状态为j时 的最短距离
最终我们要输出的就是无论最终走到哪一个点,只要是走过所有奶酪点的最短距离
举例来说,可以使用二进制数\(10110101\) 表示当前已经走过了第1 3 5 6 8号奶酪(是从低位往高位看)
那么有几个奶酪 二进制就有几位
f[i][j]表示的是最短距离 那我们要初始化为INF 二进制的INF
我们用a[x][y]表示xy的距离 利用题目要求的两点间距离公式
注意点 当我们从原点第一次开始吃奶酪的时候这个初始距离是原点到奶酪的距离
这时候的f[0][1 << i - 1]表示走到第i个奶酪且唯一走过第i个奶酪的状态
1 << i - 1是只有奶酪i为1其余奶酪权威0的二进制数表示的值\(2 ^ {i - 1}\)
例如一共8个奶酪只走了第3号奶酪就是00000100 也就是\(2 ^ {3 - 1} = 4\)

接下来是三层循环,分别枚举所有可能的二进制状态、当前点所在的位置和能在当前状态下到达当前点的位置。
在第二层循环中要判断一下i在当前二进制状态下是否已走过,如果根本没走过则不需要进行接下来的计算,直接continue就可以。因为你必须要把当前奶酪点走过了 你这个时候的f[i][j]才是走过当前点的路径
在第三层运算中同样要判断当前点j是否已走过,且当前点j不与i点相同。
接下来就可以转移状态了
走过当前点i的状态就是走过点j且没走过点i的状态+ij之间的距离a[i][j]
好了 题意和做题方法搞明白了 上代码!
耗时3小时!不过是看了题解一遍后独立写的代码
但是debug了好久
注意
一个数是double型 那么计算这个数所用的数也应是double
a[i][j] = sqrt((xx[i] - xx[j]) * (xx[i] - xx[j]) + (yy[i] - yy[j]) * (yy[i] - yy[j]))这里的xx[]和yy[]就必须是double

刚开始debug很久的原因就是括号没加够 当你分不清<< + - & 等符号谁的运算顺序更高 就加上括号
if ((k & (1 << i - 1)) == 0) continue;

学会了保留几位小数的方式
注意输入double型是scanf("%lf%lf", &xx[i], &yy[i])
输出时保留小数
printf("%.2lf\n", res); C
cout << fixed << setprecision(2) << res << endl; (fixed必须加) C++

#include <bits/stdc++.h>

using namespace std;

const int N = 16, M = 1 << N ; //M是N的二进制上限值 //这里N还不能开到20 底下的二维数组会超量 

double a[N][N];
double xx[N], yy[N]; //分别存每个点的坐标 规避坐标负数问题 也要定义成double型的原因是sqrt()会根据里面的型来开根 
double f[N][M]; //状态方程 
int n; 

void solve()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i ++ ) scanf("%lf%lf", &xx[i], &yy[i]); 
    memset(f, 127, sizeof f); //初始化f数组 最短距离为无穷 
    double res = f[0][0]; //将答案初始化为无穷大 输出要用 

    //预处理两点之间距离 
    for(int i = 0; i <= n; i ++ )
    {
        for (int j = i + 1; j <= n; j ++ )
        {
                a[i][j] = sqrt((xx[i] - xx[j]) * (xx[i] - xx[j]) + (yy[i] - yy[j]) * (yy[i] - yy[j]));
                a[j][i] = a[i][j];
        }
    }
    //cout << a[0][n] << endl;
    //对第一次吃奶酪走到的i进行初始化
    for (int i = 1; i <= n; i ++ ) 
    {
        f[i][1 << (i - 1)] = a[0][i];
    }
    //cout << f[2][1 << 1] << endl;
    //开始dp
    for (int k = 1; k <= (1 << n) - 1; k ++ ) //枚举所有二进制状态
    {
        for (int i = 1; i <= n; i ++ )
        {
            if ((k & (1 << i - 1)) == 0) continue; //如果当前二进制状态k并没有走过奶酪i(这个式子就等于1) 跳过本次循环,如果走过了这个式子就等于0 
            for (int j = 1; j <= n; j ++ )
            {
                if ((k & (1 << j - 1)) == 0) continue;
                if (i == j) continue; //i和j是一个点也没必要搜了 退出
                //开始转移状态!
                f[i][k] = min(f[i][k], f[j][k - (1 << i - 1)] + a[i][j]); 

            } 
        }
    } 
    //cout << f[1][(1 << n) - 1] << endl;

    //从以奶酪点i为路径终点的状态取出最短路径
    for (int i = 1; i <= n; i ++ ) 
    {
        res = min(res, f[i][(1 << n) - 1]); //注意这里的f[i][(1 << n) - 1]   (1 << n) - 1表示的是所有奶酪点都走过了 比如 (1 << 8) - 1 表示七个奶酪点都走过了 1111111 
    }
    printf("%.2lf\n", res);
    //cout << fixed << setprecision(2) << res << endl;
    return;
}

int main()
{
    solve();

    return 0;
}

还是非常有成就感的!

posted @ 2024-04-16 15:59  MsEEi  阅读(31)  评论(0)    收藏  举报