P1433 吃奶酪

文章介绍了一个基于动态规划的算法,用于计算小老鼠吃掉n块奶酪的最短行走距离,利用状压DP方法存储中间状态并进行状态转移。

题目描述

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

输入格式

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

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

输出格式

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

输入输出样例

输入 #1

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

输出 #1

7.41

说明/提示

数据规模与约定

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

提示

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


2022.7.132022.7.13:新增加一组 HackHack 数据。


思路

状压dp

代码


/*用数组F[i][j]表示老鼠走到第i块奶酪,其他走过的奶酪坐标的二进制状态为j时的最短距离 (走过用1表示,没走过用0表示)
用循环来分别枚举所有可能的二进制状态、当前所在位置和在当前状态下可以到达的点
假设此时的二进制状态为s,起点为j,终点为i,用a[i][j]表示i到j之间的距离,F[j][k-(2^(i-1))]为当前位置在j 点且没有到达过i点的最短路径。

最终得状态转移方程f[i][k] = min(f[i][k], f[j][k-(2^(i-1)] + a[i][j]);*/
#include <bits/stdc++.h>//万能头文件
using namespace std;
#define min(a,b) (((a)<(b))?(a):(b)) //三目运算符,不过min函数是c++已经封装好的,因此这行可不用写
double a[20][20];从第i块到第j块的距离,使用两点之间距离公式(x1​−x2​)^2+(y1​−y2​)^2​ 
double x[20],y[20];//每块奶酪的横、纵坐标
double F[18][34000];//状压DP数组 在第i个点上,走过的二进制状态的十进制表达为j时,最短的距离 
int N; 
double dis(int v,int w){//计算第v,w个奶酪之间的距离
    return sqrt((x[v]-x[w]) * (x[v]-x[w]) + (y[v]-y[w]) * (y[v]-y[w]));//两点间距离公式 (x1​−x2​)^2+(跟上一行)(y1​−y2​)^2​

}
int main(){
    int i, j, k;
    double ans;
    memset(F, 127, sizeof(F));
    ans = F[0][0];
    scanf("%d", &N);
    for(i = 1; i <= N; i++){
        scanf("%lf%lf", &x[i], &y[i]);
    }
    x[0] = 0;
    y[0] = 0;
    for(i = 0; i <= N; i++){
        for(j = i + 1; j <= N; j++){
            a[i][j] = dis(i, j);//初始化
            a[j][i] = a[i][j];
        } 
    }
    for(i = 1; i <= N; i++){//初始化
        F[i][(1 << (i - 1))] = a[0][i];//在i点上且只有经过i点时距离是原点到i点的距离
    }
    for(k = 1; k < (1 << N); k++){//枚举所有二进制的状态
        for(i = 1; i <= N; i++){
            if((k & (1 << (i - 1))) == 0)
                continue;//i的位置没被走过,所以跳过
            for(j = 1; j <= N; j++){
                if(i == j){
                    continue;//同一个点直接跳过
                }
                if((k & (1 << (j - 1))) == 0){
                    continue;//j的位置未走过
                }    
                F[i][k] = min(F[i][k], F[j][k - (1 << (i - 1))] + a[i][j]);//递推式
            } 
        } 
    } 
    for(i = 1; i <= N; i++){
        ans = min(ans, F[i][(1 << N) - 1]);
    }
    printf("%.2f\n", ans);//保留2位小数
    return 0;
}

这是一个经典的 **旅行商问题(TSP, Traveling Salesman Problem)** 的变种:老鼠从起点 `(0, 0)` 出发,吃掉所有 `n` 块奶酪,每块奶酪只能吃一次,求最短路径总长度。 由于 `n ≤ 15`,我们可以使用 **状态压缩动态规划(DP + Bitmask)** 来解决这个问题。 --- ### ✅ 解题思路 1. **预处理距离**: - 计算任意两个奶酪之间的欧几里得距离。 - 同时计算从起点 `(0,0)` 到每一块奶酪的距离。 2. **状态表示**: - 使用一个整数 `mask` 表示当前已经吃过的奶酪集合(二进制中第 `i` 位为 1 表示第 `i` 块奶酪已吃)。 - `dp[mask][i]` 表示:当前已经吃过的奶酪集合为 `mask`,且最后吃的是第 `i` 块奶酪时的最短路径长度。 3. **状态转移**: - 对于每个状态 `mask` 和当前位置 `i`,尝试走到下一个未访问的奶酪 `j`: ``` dp[mask | (1 << j)][j] = min(dp[mask | (1 << j)][j], dp[mask][i] + dist[i][j]) ``` 4. **初始化**: - 从 `(0,0)` 出发到第 `i` 块奶酪: ``` dp[1 << i][i] = dist_start[i] ``` 5. **答案**: - 枚举所有以某个奶酪结尾、且吃完了全部奶酪的状态(即 `mask == (1 << n) - 1`),取最小值。 --- ### ✅ C++ 代码实现 ```cpp #include <iostream> #include <vector> #include <cmath> #include <iomanip> #include <algorithm> using namespace std; const int MAXN = 15; const double INF = 1e15; struct Point { double x, y; Point(double x = 0, double y = 0) : x(x), y(y) {} }; double dist(Point a, Point b) { return hypot(a.x - b.x, a.y - b.y); // sqrt((x1-x2)^2 + (y1-y2)^2) } int main() { int n; cin >> n; vector<Point> cheeses; cheeses.push_back(Point(0, 0)); // index 0 is start point (0,0) for (int i = 0; i < n; ++i) { double x, y; cin >> x >> y; cheeses.push_back(Point(x, y)); } // Distance from start (0,0) to each cheese vector<double> startDist(n + 1); for (int i = 1; i <= n; ++i) { startDist[i] = dist(cheeses[0], cheeses[i]); } // Distance between cheeses vector<vector<double>> d(n + 1, vector<double>(n + 1)); for (int i = 0; i <= n; ++i) { for (int j = 0; j <= n; ++j) { d[i][j] = dist(cheeses[i], cheeses[j]); } } // DP with bitmask int total = 1 << n; vector<vector<double>> dp(total, vector<double>(n, INF)); // Initialize: starting at each cheese i (from (0,0)) for (int i = 0; i < n; ++i) { dp[1 << i][i] = startDist[i + 1]; // cheese i corresponds to index i in dp, but position i+1 in cheeses } // Iterate over all masks for (int mask = 0; mask < total; ++mask) { for (int i = 0; i < n; ++i) { if ((mask & (1 << i)) == 0) continue; // cheese i not visited for (int j = 0; j < n; ++j) { if (mask & (1 << j)) continue; // already visited j int newMask = mask | (1 << j); dp[newMask][j] = min(dp[newMask][j], dp[mask][i] + d[i + 1][j + 1]); // map i -> cheese[i+1] } } } // Answer: min distance when all cheeses are eaten double ans = INF; int full = total - 1; for (int i = 0; i < n; ++i) { ans = min(ans, dp[full][i]); } cout << fixed << setprecision(2) << ans << endl; return 0; } ``` --- ### 🔍 代码解释 - `cheeses[0]` 是起点 `(0,0)`,其余 `cheeses[1..n]` 是奶酪点。 - `dp[mask][i]` 中 `i` 是奶酪编号(0~n-1),对应实际坐标是 `cheeses[i+1]`。 - 我们用 `hypot` 函数安全计算欧氏距离,避免溢出。 - 状态总数是 `2^n`,每个状态最多更新 `n` 次,时间复杂度约为 `O(n² × 2ⁿ)`,对于 `n=15` 是可接受的(约 `15² × 32768 ≈ 7e6`)。 --- ### 📌 示例运行说明 输入: ``` 4 1 1 1 -1 -1 1 -1 -1 ``` 输出: ``` 7.41 ``` 这表示老鼠从 (0,0) 出发,走完四个角落的奶酪,最短路径约为 7.41。 --- ### ✅ 相关优化建议 - 可以将奶酪索引从 0 开始统一处理,减少 `+1/-1` 映射。 - 使用 `float` 不够精度,必须用 `double`。 - 输出保留两位小数:`setprecision(2) << fixed`。 ---
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值