P1433 吃奶酪——dfs+状态压缩剪枝

吃奶酪

题目描述

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

输入格式

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

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

输出格式

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

样例 #1

样例输入 #1

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

样例输出 #1

7.41

提示

数据规模与约定

对于全部的测试点,保证 1 ≤ n ≤ 15 1\leq n\leq 15 1n15 ∣ x i ∣ , ∣ y i ∣ ≤ 200 |x_i|, |y_i| \leq 200 xi,yi200,小数点后最多有 3 3 3 位数字。

提示

对于两个点 ( x 1 , y 1 ) (x_1,y_1) (x1,y1) ( x 2 , y 2 ) (x_2, y_2) (x2,y2),两点之间的距离公式为 ( x 1 − x 2 ) 2 + ( y 1 − y 2 ) 2 \sqrt{(x_1-x_2)^2+(y_1-y_2)^2} (x1x2)2+(y1y2)2


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

分析

  1. 此题的思路不难,但是想做对挺不容易的,一看n的大小15,就可以想到用全排列,暴力逐个判断走完每个点不同排列方式,找最短距离,但是如果仅仅一个简单的最优性剪枝if (dis >= ans) return;这是不够的,会有一个点TLE;需要用到状态压缩剪枝;
  2. 为什么需要状态压缩? 由于到达某个点的状态有多种情况,比如到达12,可能之前走过1 2 3,也可能走过4 5 6,所以记录状态时,可以用状态压缩,用二进制序列的十进制数来表示每个状态,用一个数组f[][],一维就是点的编号,二维就是到达该点是的状态(已走过的点组成二进制序列的十进制数);
  3. f数组存了到达不同点所经过的状态,以及他的当前最优值,如果下次换一个状态还到达该点,发现新的状态所用距离不如上一次,就直接continue(不是return,不能顺手直接return,刚开始return卡了我一天找不到错误,可能还是不够理解),当前 if (f[i][newStatus] != 0 && f[i][newStatus] <= f[lastid][status] + calDis(last, {xx, yy}))不满足,只能说明第i个点新的状态已比较优,不用再通过上一个状态连当前的点,可以去判断下一个点,也就是i++,就不能直接return走;
  4. 由于从起点0,0出发,dfs的参数就不能仅仅用lastid去获取上个点的信息(还需要加一个last存上一个点的坐标),比如(0,0)出发,搜第一个点,然后利用i=0,和lastid=0,同时查点,但起始点(0,0)并不是坐标编号为0,就会错误计算;还需要注意坐标为实数,说明还x,y可能为小数,所以double存,不然下面第二轮测试点过不去,WA掉;
    P1433 吃奶酪——dfs+状态压缩剪枝_第1张图片

P1433 吃奶酪——dfs+状态压缩剪枝_第2张图片

#include 

using namespace std;

int n;
double x, y;
double ans = 10000000;
vector<pair<double, double>> v;
double vis[20];
//状态记录 d[12][42501] 表示走到编号为12的点,所经过的点的编号为42501二进制序列为1的数
double f[20][33000];

//两个点的距离
double calDis(pair<double, double> p1, pair<double, double> p2) {
    return sqrt((p1.first - p2.first) * (p1.first - p2.first) +
                (p1.second - p2.second) * (p1.second - p2.second));
}

//lastid:上一个坐标的编号,last:上一个点的坐标,status为当前走过的点编号组成的二进制序列的十进制值
void dfs(int u, int lastid, pair<double, double> last, int status, double dis) {
    //剪枝
    if (dis >= ans)
        return;
    if (u == n) {
        ans = min(ans, dis);
        return;
    }
    for (int i = 0; i < n; i++) {
        double xx = v[i].first, yy = v[i].second;
        if (!vis[i]) {
            //更新状态,因为加上了i这个点,status + 2^i(1左移i位就是2的i的值)
            int newStatus = status + (1 << i);
            //状态压缩剪枝
            if (f[i][newStatus] != 0 && f[i][newStatus] <= f[lastid][status] + calDis(last, {xx, yy}))
                continue;
            f[i][newStatus] = f[lastid][status] + calDis(last, {xx, yy});
            vis[i] = 1;
            dfs(u + 1, i, {xx, yy}, newStatus, dis + calDis(last, {xx, yy}));
            vis[i] = 0;
        }
    }
}

int main() {
    cin >> n;
    for (int i = 0; i < n; i++) {
        cin >> x >> y;
        v.push_back({x, y});
    }
    dfs(0, 0, {0, 0}, 0, 0);
    printf("%.2lf", ans);
    return 0;
}

你可能感兴趣的:(洛谷,dfs,算法,剪枝,深度优先)