题解
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}
nn−i的概率投出新的一面,即出现
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=nn−ifi+1+nifi+1
式子变形后是
f
i
=
f
i
+
1
+
n
n
−
i
f_i=f_{i+1}+\frac{n}{n-i}
fi=fi+1+n−in
代码
#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
1≤k≤⌈2n⌉的
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
1≤n≤5000,1≤ai≤105
思路
将首先来观察一下有什么性质,首先用来建房的山是不会被操作的,如果操作的话两边的山的高度只会更低,相邻两边的操作次数不可能减少,总操作次数不减;两间房子显然是不会建在相邻的位置。根据以上两点考虑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
i−1座山不能建房子,考虑第
i
−
2
i-2
i−2座建不建房子;若建房子,要满足
a
i
−
1
<
min
(
a
i
−
2
,
a
i
)
a_{i-1}<\min(a_{i-2} , a_i)
ai−1<min(ai−2,ai),若满足则花费为
0
0
0,不满足则最少要把
a
i
−
1
a_{i-1}
ai−1到
min
(
a
i
−
2
,
a
i
)
−
1
\min(a_{i-2},a_i)-1
min(ai−2,ai)−1,即费用为
a
i
−
1
−
min
(
a
i
−
2
,
a
i
)
+
1
a_{i-1}-\min(a_{i-2},a_i)+1
ai−1−min(ai−2,ai)+1;若不建房子,则满足
a
i
−
1
<
a
i
a_{i-1}<a_i
ai−1<ai,同理不满足时费用为
a
i
−
1
−
a
i
a_{i-1}-a_i
ai−1−ai,易得转移方程:
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(fi−2,j−1,0+max(0,ai−1−ai+1),fi−2,j−1,1+max(0,ai−1−min(ai−2,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(fi−1,j,0,fi−1,j,1+max(0,ai−ai−1+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转移。