CSDN 第 8 期题解(无 T1 代码)

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 1S,T104

题解

全卷最难题,我调了三十分钟一直 90 90 90 分。既然你已经知道题意了那我觉得你应该会做了。

务必忽略 S S S T T T 中空格,不知道是什么高妙的数据。

T2

原题戳这,你已经会 IOI 2000 的题了

这题也是稍有问题,实际数据范围貌似是 n ⩽ 1 0 3 n\leqslant 10^3 n103,不知道你们怎么写上 n ⩽ 100 n\leqslant 100 n100 的。

题目大意

给定一个只包含大小写字母的字符串 S S S,你现在可以往字符串的任意一个位置插入字符,并且可以插入若干次,问最少插入多少次可以使得 S S S 变为回文串。

数据范围

n = ∣ S ∣ , 1 ⩽ n ⩽ 1 0 3 n=|S|,1\leqslant n\leqslant 10^3 n=S,1n103

题解

考虑到回文串是翻过去和翻过来自对称的,于是我们可以把 S S S 翻过来作为 T T T,求出 S S S T T T 的最长公共子序列,设其答案为 x x x,则总答案一定是 n − x n-x nx

于是就转化成了最长公共子序列的问题,经典 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=dpi1,j1+1,dpi,j=max(dpi1,j,dpi,j1),Si=TjSi=Tj

理解一下就是如果 S S S T T T 的这一位相同,则我们可以在这个最长公共子序列的最后一位添加 S i S_i Si,这样最长公共子序列又会变长一位,否则这位不同,我们无法增长我们的最长公共子序列。

答案是 n − d p n , n n-dp_{n,n} ndpn,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

题意也不是很清楚,没有讲清楚只能向下和向右走。

更牛一点的戳这,CSP-J 2020 T4

题目大意

给定一个 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 n100

题解

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(dpi1,j,dpi,j1)+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

原题戳这,CF148D

尽管如此 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 0a,b103

题解

感觉没有什么可以直接算的数学式子,继续考虑 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} dpi1,j2 过来,那么我要先从 i + j i+j i+j 个球里面摸一个黑球,概率 j i + j \dfrac{j}{i+j} i+jj,再从剩下 i + j − 1 i+j-1 i+j1 个球里面摸取一个黑球,概率 j − 1 i + j − 1 \dfrac{j-1}{i+j-1} i+j1j1,再从剩下 i + j − 2 i+j-2 i+j2 个球里面摸一个白球,概率 i − 1 i + j − 2 \dfrac{i-1}{i+j-2} i+j2i1
  • 这一轮 A 摸到了黑球,B 也摸到了黑球,但是 B 破坏了黑球,显然这个转移从 d p i , j − 3 dp_{i,j-3} dpi,j3 过来,要连着摸三个黑球的概率是 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+j1j1×i+j2j2

所以总状态转移方程如下:

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+j1j1×i+j2i1×dpi1,j2+i+jj×i+j1j1×i+j2j2×dpi,j3

直接做就做完了。时间复杂度 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]);
}

关于比赛的一点小建议

  1. 编辑器真的很丑,正经人起码写代码的时候字体都是 consolas,你们好歹去 vscode 搬一个编辑器过来吧。
  2. 希望你们评测代码的时候可以做一个比较具体的界面,我不是只想看我通过率多少的。
  3. 左侧题目框能不能再大一点?
  4. 为什么 ctrl+/ 不能直接注释啊,为什么多行不能同时 Tab 啊,难受的要死。
  5. 额你们要是真的实在不会出题没必要一直搬题,也可以请别人出的。
  6. 你们题面能不能用一下 markdown?
  7. 你们能不能再把题目意思说的详细一点?我们不是来猜灯谜的。 出题人但凡自己读一遍题目都知道有很多不严谨的地方。
  8. 好歹一场比赛算法分布要均匀吧,搞 3 3 3 道 DP 像什么话。
  9. 第一题不是只能出字符串题的,为什么简单题就只有字符串题呢。?
  10. 你们不能自己公布官方题解吗。? 原来你们的出题人不负责这一项的。
  11. 所以为什么每一道题不能有部分分。?
  12. 你们其实可以搞难一点。
  13. 你们这一场着实比上一场质量还下滑严重。
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值