P1433 吃奶酪(状压dp)

状压dp

状压 d p dp dp可以解决 n < = 21 n<=21 n<=21的情况。

在状压时 d p [ i ] [ j ] dp[i][j] dp[i][j],代表在第 i i i个位置时且走过二进制状态 j j j的最佳答案。

将状态压成二进制的形式去求解。

例:10100110代表经历了2、3、6、8四种状态。

时间复杂度 O ( n 2 2 n ) O(n^2 2^n) O(n22n)

题目描述

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

输入格式

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

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

输出格式

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

思路

先将每条边的距离都预处理。

还需要预处理第i个奶酪到第i个奶酪的距离。这里直接将 ( 0 , 0 ) (0,0) (0,0)的位置也加了进去,也就是用来初始化了。

for(int i = 1; i <= n; ++i) {
	dp[i][1 << (i - 1)] = f[0][i];
}

接下来三层循环。

分别枚举二进制的状态、当前点所在的位置和能在当前状态下转移到当前点的位置。

第二层循环需要判断一下 i i i在当前二进制状态下是否已走过,如果根本没走过则不需要进行接下来的计算。

第三层循环判断当前点是否已走过,且当前点不和 i i i相同。

转移方程:
d p [ i ] [ k ] = m i n ( d p [ i ] [ k ] , d p [ j ] [ k − ( 1 < < ( i − 1 ) ) ] + f [ i ] [ j ] ) dp[i][k]=min(dp[i][k],dp[j][k-(1<<(i-1))]+f[i][j]) dp[i][k]=min(dp[i][k],dp[j][k(1<<(i1))]+f[i][j])
k k k表示此时的二进制状态(指已经走过哪些点),起点为 j j j,终点为 i i i

d p [ j ] [ k − ( 1 < < ( i − 1 ) ) ] dp[j][k-(1<<(i-1))] dp[j][k(1<<(i1))]表示在 j j j点走过没有 i i i的状态的距离

最大需要的单独二进制状态为 ( 1 < < ( n − 1 ) ) (1<<(n-1)) (1<<(n1)),但是所有的二进制状态和为 ( 1 < < n ) − 1 (1<<n)-1 (1<<n)1

#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
#define DOF 0x7f7f7f7f
#define endl '\n'
#define mem(a, b) memset(a, b, sizeof(a))
#define debug(case, x) cout << case << "  : " << x << endl
#define open freopen("ii.txt", "r", stdin)
#define close freopen("oo.txt", "w", stdout)
#define IO                       \
    ios::sync_with_stdio(false); \
    cin.tie(0);                  \
    cout.tie(0)
#define pb push_back
using namespace std;
//#define int long long
#define lson rt << 1
#define rson rt << 1 | 1
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<long long, long long> PII;
const int maxn = 1e6 + 10;

double f[20][20];
double x[20], y[20];
double dp[18][(1 << 15) + 5];

double dis(int i, int j) {
    return sqrt((x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j]) * (y[i] - y[j]));
}

int main() {
    double ans = 1e18;
    for(int i = 1; i <= 15; ++i) {
        for(int j = 1; j <= ((1 << 15) + 8); ++j) {
            dp[i][j] = 1e18;
        }
    }

    int n;
    scanf("%d", &n);
    x[0] = y[0] = 0;
    for(int i = 1; i <= n; ++i) {
        scanf("%lf%lf", &x[i], &y[i]);
    }

    for(int i = 0; i < n; ++i) {
        for(int j = i + 1; j <= n; ++j) {
            f[i][j] = f[j][i] = dis(i, j);
        }
    }

    for(int i = 1; i <= n; ++i) {
        dp[i][1 << (i - 1)] = f[0][i];
    }
    for(int k = 1; k < (1 << n); ++k) {
        for(int i = 1; i <= n; ++i) {
            if((k & (1 << (i - 1))) == 0) continue;
            for(int j = 1; j <= n; ++j) {
                if(i == j) continue;
                if((k & (1 << (j - 1))) == 0) continue;
                dp[i][k] = min(dp[i][k], dp[j][k - (1 << (i - 1))] + f[i][j]);
            }
        }
    }
    for(int i=1;i<=n;++i){
        ans=min(ans,dp[i][(1<<n)-1]);
    }
    printf("%.2f\n",ans);

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值