papamelon 255. 贿赂囚犯 Bribe the Prisoners

https://www.papamelon.com/problem/255

一个监狱里有 P 个并排着的牢房, 从左到右依次编号为 1,2,...P
最初所有的牢房里都住着一个囚犯。相邻的两个牢房之间有一个窗户, 可以通过它与相邻的牢房里的囚犯对话。
现在要释放一批囚犯, 如果释放某个牢房的囚犯, 其相邻的牢房里的囚犯就会知道, 因而发怒暴动。
所以释放某个牢房的囚犯时, 必须要贿赂两旁相邻的囚犯一枚金币。
另外为了防止释放的消息在相邻牢房传开, 不仅两旁直接相邻的牢房, 所有可能听到消息的囚犯, 
即直到空牢房或者到监狱两端为止, 此间的所有囚犯都必须给一枚金币。
现在要释放 a_1,a_2...a_Q牢房里的 Q 名囚犯, 释放的顺序还未确定。
如果选择所需金币数尽量少的顺序释放, 最少需要多少金币?

输入
第一行整数 T(1≤T≤100),表示有多少组测试数据
每组测试数据有 2 行:
第一行有两个整数 P 和 Q
第二行有 Q 个整数,表示 a_1,a_2...a_Q
1≤P≤10000,1≤Q≤100
输出
输出 T 行,每行格式:Case #{第几组测试数据}: {答案},表示第几组测试数据,答案是多少
样例 1
输入
1
20 3
3 6 14
输出
Case #1: 35

如图

假如我们释放14号牢房囚犯,那么计算当前需要的花费 ,再加上释放1到13号房间中的3号和6号的囚犯的最小花费,就可以得到答案1
假如我们释放3号牢房囚犯,那么计算当前需要的花费 ,再加上释放4到20号房间中的14号和6号的囚犯的最小花费,就可以得到答案2
假如我们释放6号牢房囚犯,那么计算当前需要的花费 ,再加上释放1到5号房间中的3号囚犯的最小花费,再加上释放7到20号房间中的14号囚犯的最小花费,就可以得到答案3
答案123中的最小花费就是最后的实际答案。
我们可以使用递归来计算 使用dp[x][y]记录 释放x到y之间所有囚犯的最小开销。 但是由于P的范围太大,数组内存过大,这里使用哈希表记录释放x到y之间所有囚犯的最小开销。
用来避免重复计算.
代码

#include <iostream>
#include <algorithm>
#include <memory.h>
#include <unordered_map>

using namespace std;

int t, p, q;
const int N = 10010;
int arr[N];
unordered_map<long long, int > dp;

int dfs(int rooml, int roomr, int arrl, int arrr) {
	if (roomr < rooml) return 0;
	if (arrr < arrl) return 0;
	long long key = rooml + roomr * 100000;
	if (dp.count(key) != 0) return dp[key];
	//if (dp[rooml][roomr] != 0x3f3f3f3f) return dp[rooml][roomr];

	int ans  = 0x3f3f3f3f;
	for (int i = arrl; i <= arrr; i++) {
		int selRoom = arr[i];
		int c = (selRoom - rooml);
		int d = (roomr - selRoom);
		int a = dfs(rooml, selRoom - 1, arrl, i - 1);
		int b = dfs(selRoom + 1, roomr, i + 1, arrr);
		ans = min(ans, a + b + c + d);
	}
	dp[key] = ans;
	return ans;
}


void solve(int curr) {
	dp.clear();
	cin >> p >> q;
	for (int i = 1; i <= q; i++) {
		cin >> arr[i];
	}
	sort(arr + 1, arr + 1 + q);
	int ans = 0x3f3f3f3f;
	for (int i = 1; i <= q; i++) {
		int selRoom = arr[i];
		int c = (selRoom - 1);
		int d = (p - selRoom);
		int a = dfs(1, selRoom - 1, 1, i - 1);
		int b = dfs(selRoom + 1, p, i + 1, q);
		ans = min(ans, a + b + c + d);
	}

	cout << "Case #" << curr << ": " << ans << endl;
}


int main()
{
	cin >> t;
	for (int i = 1; i <= t; i++) {
		solve(i);
	}

	return 0;
}

下面再来一个dp数组优化内存版本的方案
使用dp[x][y]记录 释放x到y房间之间的所有囚犯的最小开销。 但是由于P的范围太大,数组内存过大.无法使用这个数组.
使用arr[N]的数组记录要释放的囚犯号码, 可以考虑dp[x][y]表示释放arr[x]~~arr[y]之间的所有囚犯的最小开销。
dp[x][y] = min(dp[x][x+1]+dp[x+1][y] , dp[x][x+2],dp[x+2][y] ... ,dp[x][y-1]+dp[y-1][y]);

#include <iostream>
#include <memory.h>
#include <algorithm>

using namespace std;

const int N = 110;
int arr[N];
int dp[N][N];
int t, n, l;


int dpFunc(int l, int r) {
	if ( r <=l+1) {
		dp[l][r] = 0;
	}

	if (dp[l][r] != -1) return dp[l][r];
	int ans = 0x3f3f3f3f;
	for (int i = l + 1; i < r; i++) {
		ans = min(ans, (arr[r] - arr[i]-1)   + (arr[i]-arr[l]-1) + dpFunc(l, i) + dpFunc(i, r));
	}

	dp[l][r] = ans;
	return ans;
}

void solve(int currIdx) {
	cin >> l >> n;
	memset(arr, 0, sizeof arr);
	for (int i = 1; i <= n; i++) {
		cin >> arr[i];
	}
	//sort(&arr[1],&arr[1]+n);
	arr[0] = 0; arr[n + 1] = l + 1;
	memset(dp, -1, sizeof dp);

	int ans = dpFunc(0, n + 1);

	cout << "Case #" << currIdx << ": " << ans << endl;
	return ;
}

int main() {
	cin >> t;
	for (int i = 1; i <= t; i++) {
		solve(i);
	}

	return 0;
}

我的视频题解空间

posted on 2022-09-04 00:09  itdef  阅读(33)  评论(0编辑  收藏  举报

导航