zoukankan      html  css  js  c++  java
  • 洛谷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)

    此时,我们可以得到状态转移方程为:

    [f[st][i] = min(f[st2][j] + sqrt{(x_i-x_j)^2+(y_i-y_j)^2}) ]

    其中,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
    ", 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) 个点都走过),所以答案即为

    [min_{i in [0,n-1]} f[2^n-1][i] ]

    我们是通过如下代码段来获得答案的:

    double ans = f[(1<<n)-1][0];
    for (int i = 1; i < n; i ++) ans = min(ans, f[(1<<n)-1][i]);
    printf("%.2lf
    ", ans);
    

    最后,也不要忘了输出我们的 ans,同时保留2位小数哦。

    最后的最后:

    关于数位DP,最好还是按照坐标从 (0)(n-1) 为好,因为这样的 (i) 刚好能跟状态在 ([0, 2^n-1]) 范围内的数字一一对应。所以希望还是能够按照坐标从 (0) 开始比较好。

  • 相关阅读:
    正经学C#_循环[do while,while,for]:[c#入门经典]
    Vs 控件错位 右侧资源管理器文件夹点击也不管用,显示异常
    asp.net core 获取当前请求的url
    在实体对象中访问导航属性里的属性值出现异常“There is already an open DataReader associated with this Command which must be
    用orchard core和asp.net core 3.0 快速搭建博客,解决iis 部署https无法登录后台问题
    System.Data.Entity.Core.EntityCommandExecution The data reader is incompatible with the specified
    初探Java设计模式3:行为型模式(策略,观察者等)
    MySQL教程77-CROSS JOIN 交叉连接
    MySQL教程76-HAVING 过滤分组
    MySQL教程75-使用GROUP BY分组查询
  • 原文地址:https://www.cnblogs.com/quanjun/p/12375333.html
Copyright © 2011-2022 走看看