2021_GDUT_新生专题训练_动态规划基础

题解

I - Flipping Coins

传送门

题意

给定 n n n枚正面朝上的硬币,先可执行 k k k次抛硬币操作:选定一枚硬币抛,有0.5的概率正面朝上,问 k k k次操作正面朝上的硬币数量的最大期望。

思路

如果当前朝上硬币数<n,最优操作是去抛朝下的硬币,这时有0.5的概率朝上硬币数+1,有0.5的概率不变;
当朝上硬币数=n时,必须要抛一枚朝上的硬币,这时有0.5的概率朝上硬币数-1,有0.5的概率朝上硬币数不变;
算完概率后乘上对应的硬币数即可得到期望。转移方程看代码。

代码

#include <bits/stdc++.h>

#define fo(i, x, y) for (int i = (x); i <= (y); ++i)
#define fd(i, x, y) for (int i = (x); i >= (y); --i)
using namespace std;

const int maxn = 400 + 5;

double f[maxn][maxn];

int n, k;

int getint()
{
	char ch;
	int res = 0, p;
	while (!isdigit(ch = getchar()) && ch != '-');
	p = ch == '-'? ch = getchar(), -1 : 1;
	while (isdigit(ch))
		res = res * 10 + ch - '0', ch = getchar();
	return res * p;
}

int main()
{
	n = getint(); k = getint();
	f[0][0] = 1;
	fo(i, 0, k - 1)
	{
		fo(j, 0, n - 1)
		{
			f[i + 1][j] += f[i][j] * 0.5;
			f[i + 1][j + 1] += f[i][j] * 0.5;
		}
		f[i + 1][n] += f[i][n] * 0.5;
		f[i + 1][n - 1] += f[i][n] * 0.5;
	}
	double ans = 0;
	fo(i, 1, n) ans += f[k][i] * i;
	printf("%.8f\n", ans);
	return 0;
}

J - Dice (III)

传送门

题意

有一颗 n n n面的骰子,投出每一面的概率相等,求 n n n面都至少出现一次的期望投掷次数。

思路

f i f_i fi表示目前已经出现 i i i面,到出现 n n n面时的期望次数。显然 f n = 0 f_n=0 fn=0,目标状态是 f 0 f_0 f0
当前有 n − i n \frac{n-i}{n} nni的概率投出新的一面,即出现 i + 1 i+1 i+1面,根据状态定义从 i + 1 i+1 i+1面到 n n n面的期望次数是 f i + 1 f_{i+1} fi+1
i n \frac{i}{n} ni的概率投出已经出现的面,即出现 i i i面;
则转移方程: f i = n − i n f i + 1 + i n f i + 1 f_i=\frac{n-i}{n}f_{i+1}+\frac{i}{n}f_{i}+1 fi=nnifi+1+nifi+1
式子变形后是 f i = f i + 1 + n n − i f_i=f_{i+1}+\frac{n}{n-i} fi=fi+1+nin

代码

#include <bits/stdc++.h>

#define fo(i, x, y) for (int i = (x); i <= (y); ++i)
#define fd(i, x, y) for (int i = (x); i >= (y); --i)
using namespace std;

const int maxn = 1e5 + 5;

int n;
double f[maxn];

int getint()
{
	char ch;
	int res = 0, p;
	while (!isdigit(ch = getchar()) && ch != '-');
	p = ch == '-'? ch = getchar(), -1 : 1;
	while (isdigit(ch))
		res = res * 10 + ch - '0', ch = getchar();
	return res * p;
}

void work(int kase)
{
	n = getint();
	f[n] = 0;
	fd(i, n - 1, 0) f[i] = f[i + 1] + 1.0 * n / (n - i);
	printf("Case %d: %.8f\n", kase, f[0]);
}

int main()
{
	int T;
	T = getint();
	fo(i, 1, T) work(i);
	return 0;
}

L - Hills

传送门

题意

给定 n n n座山的高度 { a i } \{a_i\} {ai},如果某座山的高度严格高于相邻的山,则可在这座山上建房子。现可对这些山进行操作:指定一座山令其高度-1,花费1h。操作可执行任意多次,山的高度可以为负。对于满足 1 ≤ k ≤ ⌈ n 2 ⌉ 1\leq k \leq \lceil \frac{n}{2}\rceil 1k2n k k k,求出建 k k k间房子的最少花费时间。
1 ≤ n ≤ 5000 , 1 ≤ a i ≤ 1 0 5 1 \leq n \leq 5000,1 \leq a_i \leq 10^5 1n5000,1ai105

思路

将首先来观察一下有什么性质,首先用来建房的山是不会被操作的,如果操作的话两边的山的高度只会更低,相邻两边的操作次数不可能减少,总操作次数不减;两间房子显然是不会建在相邻的位置。根据以上两点考虑dp。
f i , j , k f_{i,j,k} fi,j,k表示前 i i i座山,建 j j j间房子, k = 0 / 1 k=0/1 k=0/1表示第 i i i座山是否用来间房子的最少操作次数。
考虑第 i i i座山建不建房子,如果第 i i i座山建房子,第 i − 1 i-1 i1座山不能建房子,考虑第 i − 2 i-2 i2座建不建房子;若建房子,要满足 a i − 1 < min ⁡ ( a i − 2 , a i ) a_{i-1}<\min(a_{i-2} , a_i) ai1<min(ai2,ai),若满足则花费为 0 0 0,不满足则最少要把 a i − 1 a_{i-1} ai1 min ⁡ ( a i − 2 , a i ) − 1 \min(a_{i-2},a_i)-1 min(ai2,ai)1,即费用为 a i − 1 − min ⁡ ( a i − 2 , a i ) + 1 a_{i-1}-\min(a_{i-2},a_i)+1 ai1min(ai2,ai)+1;若不建房子,则满足 a i − 1 < a i a_{i-1}<a_i ai1<ai,同理不满足时费用为 a i − 1 − a i a_{i-1}-a_i ai1ai,易得转移方程:
f i , j , 1 = min ⁡ ( f i − 2 , j − 1 , 0 + max ⁡ ( 0 , a i − 1 − a i + 1 ) , f i − 2 , j − 1 , 1 + max ⁡ ( 0 , a i − 1 − min ⁡ ( a i − 2 , a i ) + 1 ) ) f_{i,j,1} = \min(f_{i - 2,j - 1,0} + \max(0, a_{i-1} - a_i + 1), f_{i - 2,j - 1,1} + \max(0, a_{i - 1} - \min(a_{i - 2}, a_i) + 1)) fi,j,1=min(fi2,j1,0+max(0,ai1ai+1),fi2,j1,1+max(0,ai1min(ai2,ai)+1))
i i i座山不建房子的情况同理:
f i , j , 0 = min ⁡ ( f i − 1 , j , 0 , f i − 1 , j , 1 + max ⁡ ( 0 , a i − a i − 1 + 1 ) ) f{i,j,0} = \min(f_{i - 1,j,0}, f_{i - 1,j,1} + \max(0, a_i - a_{i - 1} + 1)) fi,j,0=min(fi1,j,0,fi1,j,1+max(0,aiai1+1))

代码

#include <bits/stdc++.h>

#define fo(i, x, y) for (int i = (x); i <= (y); ++i)
#define fd(i, x, y) for (int i = (x); i >= (y); --i)
using namespace std;

const int maxn = 5e3 + 5, inf = 1e9;

int n;
int h[maxn];
int f[maxn][maxn][2];

int getint()
{
	char ch;
	int res = 0, p;
	while (!isdigit(ch = getchar()) && ch != '-');
	p = ch == '-'? ch = getchar(), -1 : 1;
	while (isdigit(ch))
		res = res * 10 + ch - '0', ch = getchar();
	return res * p;
}

int main()
{
	n = getint();
	fo(i, 1, n) h[i] = getint();
	fo(i, 0, n)
		fo(j, 0, n) f[i][j][0] = f[i][j][1] = 1e9;
	f[0][0][0] = f[1][0][0] = f[1][1][1] = 0;
	fo(i, 2, n)
		fo(j, 0, (i + 1) / 2)
		{
			f[i][j][0] = min(f[i - 1][j][0], f[i - 1][j][1] + max(0, h[i] - h[i - 1] + 1));
			if (j) f[i][j][1] = min(f[i - 2][j - 1][0] + max(0, h[i - 1] - h[i] + 1), f[i - 2][j - 1][1] + max(0, h[i - 1] - min(h[i - 2], h[i]) + 1));
		}
	fo(i, 1, (n + 1) / 2 - 1)
		printf("%d ", min(f[n][i][0], f[n][i][1]));
	printf("%d\n", min(f[n][(n + 1) / 2][0], f[n][(n + 1) / 2][1]));
	return 0;
}

知识总结

本专题的dp的期望和概率部分还需要再补一补,感觉还是学得一知半解。其他的题目难度不是很大,定义好状态后方程显而易见。
一些体会:
个人认为dp最重要的是状态的定义,不同的状态定义转移方程的书写难度甚至时间复杂度都会不一样。定义完状态后只需严格按照定义方程的书写也不是很困难。发现的一些比较难的dp题是需要状态不好定义,需要挖掘题目的性质的。接下来还要进一步深入学习的是dp优化,根据单调性优化dp转移。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值