洛谷 P1433 吃奶酪【状态压缩dp】

原题链接:https://www.luogu.com.cn/problem/P1433

题目描述

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

输入格式

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

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

输出格式

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

输入输出样例

输入 #1

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

输出 #1

7.41

说明/提示

数据规模与约定

对于全部的测试点,保证 1≤n≤15,∣xi​∣,∣yi​∣≤200,小数点后最多有 3 位数字。

提示

对于两个点 (x1​,y1​),(x2​,y2​),两点之间的距离公式为\sqrt{(x1-x2)^2+(y1-y2)^2}​。


2022.7.13:新增加一组 Hack 数据。

解题思路:

首先我们知道一个性质就是俩点之间线段最短,那么我们在任意俩点之间肯定是通过他们之间的最短线段走过去不拐弯才能保证距离最短,起点是(0,0),我们需要依次走到每个点吃掉所有奶酪,那么此时影响我们走过的路径的距离的唯一因素就是走过的每个点的顺序,也就是说要按哪种顺序走才能保证走过的总距离最短呢?

首先最简单的方式就是爆搜所有路径,看哪条路径走过的总距离最短,爆搜的时间复杂度就是O(n!),n=15,那么时间复杂度15!,时间复杂度非常高肯定过不了,那么我们看看能不能有某种优化方式呢,首先我们定义f[i]表示到达点i的所有路径的最小值,f[i]可以用来更新后面的若干个状态,也就是说爆搜的的情况下会有很多状态存在多次计算,也就是说存在一些重复的计算,这个时候就可以想到dp,因为dp就是用来优化掉一些重复计算的,然后我们可以发现n=15,n是非常小的,通过n非常小这个性质我们就可以知道这个题目八九不离十就是状态压缩dp了呀,下面考虑状态压缩dp的处理。

状态定义:

f[i][j]表示当前走过的点的状态为i,当前所在位置为j走过的距离的最小值

初始化:

当前状态为0时,当前只能在起点,所以f[0][0]=0,f[0][i]=正无穷(0

状态转移:

由于我们当前所在位置为j,我们还需要在状态i中选择一个除j之外的其他位置作为上一个所在位置用于状态转移

i^(1<

  • f[i][j] = min(f[i][j], f[i ^ (1 << j)][k] + d[j][k]);

最终答案

由于经过所有点之后的终点可以是任意一个点,所以答案是max(f[(1<

时间复杂度:上述状态压缩dp时间复杂度为O((2^n)*n*n),n最大是15,所以时间复杂度大概是7e6,这个时间复杂度是肯定可以过的。

cpp代码如下:

#include 
#include 
#include 
#include 
#include 

using namespace std;
typedef pair PDD;
#define x first
#define y second
const int N = 17;

int n;
PDD points[N];
double d[N][N];
double f[1 << N][N];
double get_distance(PDD u, PDD v)
{
    double x1 = u.x, y1 = u.y;
    double x2 = v.x, y2 = v.y;
    return sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
}
int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        scanf("%lf%lf", &points[i].x, &points[i].y);
        for (int j = 0; j < i; j++)
            d[j][i] = d[i][j] = get_distance(points[i], points[j]); // 计算好所有奶酪所在点之间的距离
    }

    // 初始化
    for (int i = 0; i < 1 << n; i++)
        for (int j = 0; j <= n; j++)
            f[i][j] = 1e18;

    f[0][0] = 0; // 最开始在(0,0)除,所以f[0][0]=0,其他的f[0][i]=正无穷(表示这些状态不存在的,不合法的)
    for (int i = 1; i < 1 << n; i++)
        for (int j = 1; j <= n; j++)
        {
            if ((i & (1 << (j - 1))) == 0) // 当前所在位置j必须在当前经过的城市的状态i中
                continue;
            if ((i ^ (1 << (j - 1))) == 0) // 最开始是由(0,0)出发,到达其他的各个位置,由于(0,0)不包含在输入的这些点中,所以需要特殊处理一下
                f[i][j] = min(f[i][j], f[0][0] + d[0][j]);
            for (int k = 1; k <= n; k++) // 考虑当前位置的上一个位置,用于状态转移
            {
                if (k == j || ((i & (1 << (k - 1))) == 0)) // 上一个位置不能和当前位置相同,并且上一个位置也必须在当前经过的城市的状态i中
                    continue;
                // 状态转移,i^(1<

你可能感兴趣的:(动态规划,算法,动态规划)