KEYENCE Programming Contest 2023 Autumn(AtCoder Beginner Contest 325)G. offence(带删区间dp)

题目

给定小写字母串s(|s|<=300),以及一个上限k(k<=300),

每次操作,你可以选择当前串中的一个of子串,

删除of子串和of子串后面紧跟着的i个字符,其中i∈[0,k],删完之后串后面的部分会接上来

你可以执行任意次操作,也可以不执行,求串最少剩的长度是多少

思路来源

乱搞ac

心得

1. 一开始没考虑到删除的后面k个字符非连续的情形,于是是枚举的删到哪里,然后wa了

非连续是指,先删后面的一段of,再删前面的of,导致第二次删的在原串中不一定连续

2. 发现这个点之后,想了很久,一开始是想加一维表示还能最多用多少次删的机会,

后来想了想发现这个值可以和dp维护的最小剩的数量合并,直接减k就可以了

3. 减k之后,一开始没考虑s[l]=='o'时dfs(l+1,r),

后来加上枚举划分轴通过了,但是不知道怎么通过的,直至申老师构造出了一种样例

oofaaa

3

此时,如果不舍弃掉第一个o的话,就得不到ans=1的答案

也就是不一定每个o都是要和f匹配的,舍弃当前o,也是一种决策

题解

其实就是枚举每个o的决策,假设当前在[l,r]

1. 如果s[l]不为'o',舍弃当前字符,考虑[l+1,r]

否则,s[l]='o'

① 舍弃当前o,考虑[l+1,r]

② 串最左侧是of,考虑[l+2,r]删完后最少剩几个字符,还可以减k

③ 串最左侧是o,中间第i个字符是f,但o和f之间这一段可以被删完,

那么先删完这一段,再用这个of,考虑[i+1,r]删完后最少剩几个字符,还可以减k

代码1

加上舍弃o的决策

#include<bits/stdc++.h>
using namespace std;
const int N=305;
char s[N];
int n,k,dp[N][N];
int dfs(int l,int r){
	if(l>r)return 0;
	int &ans=dp[l][r];
	if(~ans)return ans;
	if(l==r)return ans=1;
	ans=min(r-l+1,1+dfs(l+1,r));
	if(s[l]=='o'){
		for(int i=l+1;i<=r;++i){
			if(s[i]=='f' && dfs(l+1,i-1)==0){
				ans=min(ans,dfs(i+1,r)-k);
			}
		}
		ans=max(ans,0);
	}
	return ans;
}
int main(){
	memset(dp,-1,sizeof dp);
	scanf("%s",s+1);
	n=strlen(s+1);
	scanf("%d",&k);
	printf("%d\n",dfs(1,n));
	return 0;
}

代码2

不加舍弃o的决策,但枚举划分轴

#include<bits/stdc++.h>
using namespace std;
const int N=305;
char s[N];
int n,k,dp[N][N];
int dfs(int l,int r){
	if(l>r)return 0;
	int &ans=dp[l][r];
	if(~ans)return ans;
	if(l==r)return ans=1;
	if(s[l]!='o')return ans=1+dfs(l+1,r);
	ans=r-l+1;
	for(int i=l+1;i<=r;++i){
		if(s[i]=='f' && dfs(l+1,i-1)==0){
			ans=min(ans,dfs(i+1,r)-k);
		}
	}
	for(int i=l;i<r;++i){
		ans=min(ans,dfs(l,i)+dfs(i+1,r));
	}
	ans=max(ans,0);
	return ans;
}
int main(){
	//while(1){
		memset(dp,-1,sizeof dp);
		//scanf("%d",&n);
		scanf("%s",s+1);
		n=strlen(s+1);
		scanf("%d",&k);
		printf("%d\n",dfs(1,n));
	//}
	return 0;
}

代码3

把记忆化搜索改成递推,需要按长度从小到大

#include<bits/stdc++.h>
using namespace std;
const int N=305;
char s[N];
int n,k,dp[N][N];
int main(){
	scanf("%s",s+1);
	n=strlen(s+1);
	scanf("%d",&k);
	for(int len=1;len<=n;++len){
		for(int l=1;l+len-1<=n;++l){
			int r=l+len-1;
			if(len==1)dp[l][r]=1;
			else if(s[l]!='o')dp[l][r]=dp[l+1][r]+1;
			else{
				dp[l][r]=r-l+1;
				for(int i=l+1;i<=r;++i){
					if(s[i]=='f' && !dp[l+1][i-1]){
						dp[l][r]=min(dp[l][r],dp[i+1][r]-k);
					}
					dp[l][r]=min(dp[l][r],dp[l][i-1]+dp[i][r]);
				}
				dp[l][r]=max(dp[l][r],0);
			}
			//printf("l:%d r:%d dp:%d\n",l,r,dp[l][r]);
		}
	}
	printf("%d\n",dp[1][n]);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小衣同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值