3 3 3 道 DP?真有你的/qiang
T1
我记得我上次好像反馈了要把题面搞好一点吧?怎么你们好像不是很在意啊。
你们 CSDN 能不能学一学别人,题意不清。
题目大意
给定一个只包含大小写字母和空格的字符串 S S S 和一个字符串 T T T(介于这个神笔数据,我不知道 T T T 是否可能含有空格),问从 S S S 中每次取出一个字母,能否组成 T T T。
希望 CSDN 别在那里做谜语人,谢谢。
数据范围
定义 ∣ S ∣ |S| ∣S∣ 表示字符串 S S S 的长度,保证对于所有数据, 1 ⩽ ∣ S ∣ , ∣ T ∣ ⩽ 1 0 4 1\leqslant |S|,|T|\leqslant 10^4 1⩽∣S∣,∣T∣⩽104。
题解
全卷最难题,我调了三十分钟一直 90 90 90 分。既然你已经知道题意了那我觉得你应该会做了。
务必忽略 S S S 和 T T T 中空格,不知道是什么高妙的数据。
T2
这题也是稍有问题,实际数据范围貌似是 n ⩽ 1 0 3 n\leqslant 10^3 n⩽103,不知道你们怎么写上 n ⩽ 100 n\leqslant 100 n⩽100 的。
题目大意
给定一个只包含大小写字母的字符串 S S S,你现在可以往字符串的任意一个位置插入字符,并且可以插入若干次,问最少插入多少次可以使得 S S S 变为回文串。
数据范围
n = ∣ S ∣ , 1 ⩽ n ⩽ 1 0 3 n=|S|,1\leqslant n\leqslant 10^3 n=∣S∣,1⩽n⩽103。
题解
考虑到回文串是翻过去和翻过来自对称的,于是我们可以把 S S S 翻过来作为 T T T,求出 S S S 与 T T T 的最长公共子序列,设其答案为 x x x,则总答案一定是 n − x n-x n−x。
于是就转化成了最长公共子序列的问题,经典 DP,设 d p i , j dp_{i,j} dpi,j 表示 S S S 中前 i i i 位与 T T T 中前 j j j 位的最长公共子序列长度为 d p i , j dp_{i,j} dpi,j。有状态转移方程如下:
{ d p i , j = d p i − 1 , j − 1 + 1 , S i = T j d p i , j = max ( d p i − 1 , j , d p i , j − 1 ) , S i ≠ T j \begin{cases} dp_{i,j}=dp_{i-1,j-1}+1,&S_i=T_j\\ dp_{i,j}=\max(dp_{i-1,j},dp_{i,j-1}),&S_i\neq T_j \end{cases} {dpi,j=dpi−1,j−1+1,dpi,j=max(dpi−1,j,dpi,j−1),Si=TjSi=Tj
理解一下就是如果 S S S 和 T T T 的这一位相同,则我们可以在这个最长公共子序列的最后一位添加 S i S_i Si,这样最长公共子序列又会变长一位,否则这位不同,我们无法增长我们的最长公共子序列。
答案是 n − d p n , n n-dp_{n,n} n−dpn,n。时间复杂度 O ( n 2 ) \mathcal{O}(n^2) O(n2)。
代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e3+5;
char a[N],b[N];
int n,dp[N][N];
int main(){
scanf("%s",a+1);
n=strlen(a+1);
for(register int i=1;i<=n;i++) b[i]=a[n-i+1];
for(register int i=1;i<=n;i++){
for(register int j=1;j<=n;j++){
if(a[i]==b[j]) dp[i][j]=dp[i-1][j-1]+1;
else dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}
printf("%d",n-dp[n][n]);
}
T3
题意也不是很清楚,没有讲清楚只能向下和向右走。
题目大意
给定一个 n × n n\times n n×n 的矩阵,从 ( 1 , 1 ) (1,1) (1,1) 开始,每次可以挪动到 ( i + 1 , j ) (i+1,j) (i+1,j) 或者 ( i , j + 1 ) (i,j+1) (i,j+1),不能移出矩阵,求 ( 1 , 1 ) (1,1) (1,1) 到 ( n , n ) (n,n) (n,n) 的路径上所有数之和的最小值。
数据范围
n ⩽ 100 n\leqslant 100 n⩽100。
题解
DP 入门题。
这真的是一眼 DP 了,稍微想想你都可以发现贪心显然是错的:
5 1 1 1 1
5 5 5 5 100
1 5 5 5 100
1 5 5 5 100
1 1 1 1 1
上面这个数据如果贪心去做显然一定会先往右边走,但是往下走更优,贪心不成立。
考虑 DP,设 d p i , j dp_{i,j} dpi,j 表示走到 ( i , j ) (i,j) (i,j) 可以取到的最少的数,则很显然有状态转移方程:
d p i , j = min ( d p i − 1 , j , d p i , j − 1 ) + a i , j dp_{i,j}=\min(dp_{i-1,j},dp_{i,j-1})+a_{i,j} dpi,j=min(dpi−1,j,dpi,j−1)+ai,j
然后就做完了。时间复杂度 O ( n 2 ) \mathcal{O}(n^2) O(n2)。
#include<bits/stdc++.h>
using namespace std;
const int N=105;
int a[N][N],dp[N][N];
int main(){
int n;
scanf("%d",&n);
for(register int i=1;i<=n;i++){
for(register int j=1;j<=n;j++){
scanf("%d",&a[i][j]);
}
}
memset(dp,0x3f,sizeof(dp));
dp[1][1]=a[1][1];
for(register int i=1;i<=n;i++){
for(register int j=1;j<=n;j++){
if(i==1&&j==1) continue;
if(i>1) dp[i][j]=min(dp[i-1][j]+a[i][j],dp[i][j]);
if(j>1) dp[i][j]=min(dp[i][j-1]+a[i][j],dp[i][j]);
}
}
printf("%d",dp[n][n]);
}
T4
尽管如此 CSDN 这次题目描述也不是很清晰,没有讲清楚后手是先破坏再取还是先取再破坏。
提个建议,一般这种精度题目都会有 Special Judge,来保证误差不超过多少的时候你也可以被判为正确,好在这题不卡精度。
题目大意
一个看不见内部的袋子里有 a a a 个白球和 b b b 个黑球,这些球除了颜色以外完全相同,A 和 B 两人轮流从袋子里等概率的摸出一个球,摸出后不放回。A 先摸,B 后摸,B 摸出一个球后,会再次从袋子里等概率的选择一个球并破坏掉(如果还有球的话)。如果有一方摸到了白球,则游戏结束,摸到的这一方胜利,如果游戏结束时两人都没有摸到白球,则后手胜利。问先手胜利的概率是多少。保留 9 9 9 位小数。
数据范围
0 ⩽ a , b ⩽ 1 0 3 0\leqslant a,b\leqslant 10^3 0⩽a,b⩽103。
题解
感觉没有什么可以直接算的数学式子,继续考虑 DP。设 d p i , j dp_{i,j} dpi,j 表示袋子里剩下 i i i 个白球, j j j 个黑球时,A 的胜利概率。进行如下讨论:
- 这一轮 A 摸到了黑球,B 摸到了白球,显然 B 无需破坏,游戏已经结束了,A 的胜率是 0 0 0。
- 这一轮 A 摸到了白球,B 没必要摸,那么达到这个状态的概率是 i i + j \dfrac{i}{i+j} i+ji。理解一下就是 A 在这 i + j i+j i+j 个球里面随便摸一个白色的,所以是 i i + j \dfrac{i}{i+j} i+ji。
- 这一轮 A 摸到了黑球,B 也摸到了黑球,但是 B 破坏了白球,显然这个转移从 d p i − 1 , j − 2 dp_{i-1,j-2} dpi−1,j−2 过来,那么我要先从 i + j i+j i+j 个球里面摸一个黑球,概率 j i + j \dfrac{j}{i+j} i+jj,再从剩下 i + j − 1 i+j-1 i+j−1 个球里面摸取一个黑球,概率 j − 1 i + j − 1 \dfrac{j-1}{i+j-1} i+j−1j−1,再从剩下 i + j − 2 i+j-2 i+j−2 个球里面摸一个白球,概率 i − 1 i + j − 2 \dfrac{i-1}{i+j-2} i+j−2i−1。
- 这一轮 A 摸到了黑球,B 也摸到了黑球,但是 B 破坏了黑球,显然这个转移从 d p i , j − 3 dp_{i,j-3} dpi,j−3 过来,要连着摸三个黑球的概率是 j i + j × j − 1 i + j − 1 × j − 2 i + j − 2 \dfrac{j}{i+j}\times\dfrac{j-1}{i+j-1}\times \dfrac{j-2}{i+j-2} i+jj×i+j−1j−1×i+j−2j−2。
所以总状态转移方程如下:
d p i , j = i i + j + j i + j × j − 1 i + j − 1 × i − 1 i + j − 2 × d p i − 1 , j − 2 + j i + j × j − 1 i + j − 1 × j − 2 i + j − 2 × d p i , j − 3 dp_{i,j}=\dfrac{i}{i+j}+\dfrac{j}{i+j}\times\dfrac{j-1}{i+j-1}\times\dfrac{i-1}{i+j-2}\times dp_{i-1,j-2}+\dfrac{j}{i+j}\times\dfrac{j-1}{i+j-1}\times\dfrac{j-2}{i+j-2}\times dp_{i,j-3} dpi,j=i+ji+i+jj×i+j−1j−1×i+j−2i−1×dpi−1,j−2+i+jj×i+j−1j−1×i+j−2j−2×dpi,j−3
直接做就做完了。时间复杂度 O ( n 2 ) \mathcal{O}(n^2) O(n2)。
#include<bits/stdc++.h>
using namespace std;
const int N=1e3+5;
double dp[N][N];
int main(){
int w,b;
scanf("%d %d",&w,&b);
for(register int i=0;i<=b;i++) dp[0][i]=0;
for(register int i=1;i<=w;i++) dp[i][0]=1;
for(register int i=1;i<=w;i++){
for(register int j=1;j<=b;j++){
dp[i][j]+=i*1.0/(i+j);
if(j>=3) dp[i][j]+=j*1.0/(i+j)*((j-1)*1.0/(i+j-1))*((j-2)*1.0/(i+j-2))*dp[i][j-3];
if(j>=2) dp[i][j]+=j*1.0/(i+j)*((j-1)*1.0/(i+j-1))*(i*1.0/(i+j-2))*dp[i-1][j-2];
}
}
printf("%.9lf",dp[w][b]);
}
关于比赛的一点小建议
- 编辑器真的很丑,正经人起码写代码的时候字体都是 consolas,你们好歹去 vscode 搬一个编辑器过来吧。
- 希望你们评测代码的时候可以做一个比较具体的界面,我不是只想看我通过率多少的。
- 左侧题目框能不能再大一点?
- 为什么
ctrl+/
不能直接注释啊,为什么多行不能同时Tab
啊,难受的要死。 - 额你们要是真的实在不会出题没必要一直搬题,也可以请别人出的。
- 你们题面能不能用一下 markdown?
- 你们能不能再把题目意思说的详细一点?我们不是来猜灯谜的。 出题人但凡自己读一遍题目都知道有很多不严谨的地方。
- 好歹一场比赛算法分布要均匀吧,搞 3 3 3 道 DP 像什么话。
- 第一题不是只能出字符串题的,为什么简单题就只有字符串题呢。?
- 你们不能自己公布官方题解吗。? 原来你们的出题人不负责这一项的。
- 所以为什么每一道题不能有部分分。?
- 你们其实可以搞难一点。
- 你们这一场着实比上一场质量还下滑严重。