当前位置: 首页 > article >正文

洛谷 P1433 吃奶酪


题目传送门


前言

虽然是一道非常基础的状压 d p dp dp(在洛谷上甚至是道驴蹄),但是我调了两个晚自习,最后发现是竟然是状态设计有问题。
所以在此篇题解中,我不但会说出正确做法,还会说出原本的代码错在哪里。
以警醒自己状态设计正确的重要性


错误思路

d p s dp_s dps 表示从 ( 0 , 0 ) (0, 0) (0,0) 走到当前状态 s s s 所用的最小路程。
那么就可以得出如下代码(每一段的具体解释见注释):

for (int si = 0; si <= ns; ++si) {  // ns 表示状态总数, 就是 (1 << n) - 1
	for (int i = 0; i < n; ++i) {  // 枚举当前所在节点 
		if (!(si & (1 << i))) continue;
		int sj = si ^ (1 << i);  // 上一个状态 
		if (!sj) {  // 如果上一个状态是全 0, 那就说明是从起点转移过来的 
			dp[si] = min(dp[si], dp[sj] + dis(0, i + 1));
			continue;
		}
		
		// 若不为全 0, 那就从上一个状态中枚举前一个位置 
		for (int j = 0; j < n; ++j)
			if (sj & (1 << j))
				dp[si] = min(dp[si], dp[sj] + dis(i + 1, j + 1));
	}
}

你若把代码补全就会发现只能得 66   p t s 66 \ pts 66 pts

那为什么错呢?

在原本代码中,我们会枚举我们所在的上一个节点(即枚举 j j j)。但是我们似乎忘了,在达到状态 s j sj sj 的最小路径时,它最后到达的一个节点 【不一定】 是我们所枚举的!

假设【当前状态与位置】、【上一个状态】如下(从右往左数):
当前状态 :   0001   1101 , i = 4 ; 上一个状态 :   0001   0101 当前状态: \ 0001 \ 1101, i = 4; \\ 上一个状态: \ 0001 \ 0101 \\ 当前状态: 0001 1101,i=4;上一个状态: 0001 0101
假如我们所枚举的上一个节点 j = 5 j = 5 j=5,我们由 d p s j + d i s ( i = 4 , j = 5 ) dp_{sj} + dis(i = 4, j = 5) dpsj+dis(i=4,j=5) 转移过来。
但是可能到达状态 0001   0101 0001 \ 0101 0001 0101 的结尾节点是 j ′ = 3 j' = 3 j=3,此时我们再这样转移显然就是错的。


正确思路

在我们的错误思路中,发现我们的状态还关乎于所处的最后一个节点。所以,增加一维状态:设 d p i , s i dp_{i, si} dpi,si 表示在已经走过 s i si si 的状态下,所处的最后一个节点是 i i i

那么正确的代码就呼之欲出了:

#include <bits/stdc++.h>

#define mkpr make_pair
#define fir first
#define sec second

using namespace std;

typedef long long ll;

const int maxn = 15 + 7;
const int maxs = (1 << 15) + 7;
const int inf  = 0x3f3f3f3f;

int n, ns;
double x[maxn], y[maxn];
double dp[maxn][maxs];
double dis(int a, int b) {
	return sqrt((x[a] - x[b]) * (x[a] - x[b]) + 
				(y[a] - y[b]) * (y[a] - y[b]));
}
void print(int x) {
	for (int i = 0; i < n; ++i)
		if (x & (1 << i)) putchar('1');
		else putchar('0');
}
int main() {
	scanf("%d", &n), ns = (1 << n) - 1;
	for (int i = 1; i <= n; ++i)
		scanf("%lf%lf", x + i, y + i);
	for (int i = 0; i <= n; ++i)
		for (int si = 0; si <= ns; ++si) dp[i][si] = 2e9;
	dp[0][0] = 0;
	for (int si = 0; si <= ns; ++si) {
		for (int i = 0; i < n; ++i) {
			if (!(si & (1 << i))) continue;
			int sj = si ^ (1 << i);
			if (!sj) {
				dp[i][si] = min(dp[i][si], dis(0, i + 1));
				continue;
			}
			for (int j = 0; j < n; ++j)
				if (sj & (1 << j))
					dp[i][si] = min(dp[i][si], dp[j][sj] + dis(i + 1, j + 1));
		}
		
	}

  // 结尾可能在任意一个节点, 因此要取最小值
	double ans = 2e9;
	for (int i = 0; i <= n; ++i)
		ans = min(ans, dp[i][ns]);
	printf("%.2lf\n", ans);
	return 0;
}

http://www.kler.cn/a/579980.html

相关文章:

  • Spring Cloud 负载均衡器架构选型
  • 基于51单片机多功能防盗报警系统
  • vulnhub靶场之【digitalworld.local系列】的FALL靶机
  • K8S学习之基础二十:k8s的coredns
  • 全面解读 JavaScript 模块化:模块化工具与性能优化
  • WWDG窗口看门狗原理
  • Qwen/QwQ-32B 基础模型上构建agent实现ppt自动生成
  • 显示器长时间黑屏
  • 【基于手势识别的音量控制系统】
  • 1.1 双指针专题:移动零(easy)
  • 香港服务器深度测评:AWS vs 阿里云 vs GCP 技术选型指南
  • 20天 - TCP 和 UDP 有什么区别?说说 TCP 的三次握手?TCP 是用来解决什么问题?
  • Ubuntu 下 nginx-1.24.0 源码分析 - ngx_cycle_modules
  • C++设计模式中的单例模式:从原理、应用、实践指南与常见问题和解决方案深度解析
  • Node.js和Vue CLI 安装指南(Windows 系统)
  • Python 实现非对称加密的 A 端和 B 端软件的详细步骤及代码示例
  • 电脑维修保养售后服务跟踪软件到哪里下载,佳易王电脑保养维护记录查询可导入图片管理系统操作教程
  • 零成本短视频爆款制造手册
  • gdb调试以及常用相关工具(hexdump\objdump等)
  • U1.【UVA】块问题-The Blocks Problem(补充了pair的使用)