洛谷 P1026 [NOIP2001 提高组] 统计单词个数 dp动态规划(超级详细注解!)

P1026 [NOIP2001 提高组] 统计单词个数

题目

https://www.luogu.com.cn/problem/P1026

说明

dp[i][j] 表示字符串从开始到第i个位置,分成j段后的最大单词数
sum[i][j] 表示字符串从i到j的最大单词数

AC代码

#include<bits/stdc++.h>
using namespace std;
int p,k,m,n,dp[210][201],sum[210][210]; 
string s,a[10];
inline int read()//优化读入速度 
{
	int ssum=0,ch=1;
	char c=0;
	while(c<'0'||c>'9') 
	{
		if(c=='-') ch=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9') 
	{
		ssum=ssum*10+c-'0';
		c=getchar();
	}
	return ch*ssum;
}
bool check(int l,int r)
{
	string t=s.substr(l,r-l+1);
	for(int i=1;i<=n;i++)
		if(t.find(a[i])==0) return true;
	return false;
}
void init()
{
	string ch;
	s+='0';
	//让s的下标从1开始,因为动态规划需要留出来0的位置初始化,因此动态规划的下标都是从1开始的,所以要保持一致 
	p=read();k=read();
	for(int i=1;i<=p;i++)
	{
		cin>>ch;
		s+=ch;
	}
	n=read();m=s.length()-1;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=m;i>=1;i--)
	for(int j=i;j>=1;j--)
	{
		sum[j][i]=sum[j+1][i];	
		if(check(j,i)) sum[j][i]++;
	} 
	//这里说一下为什么是倒序,我们首先需要知道find返回的字符串的位置是其开头的字符位置;
	//倒序查询的话,每次多出的字母都是在开头位置,
	//我们正好可以以此字母为基准通过find查询字典里是否有符合要求的字符串
	//同时可以保证不会重复使用开头字母,因为它是新出现的
	//如果正序遍历,新出现的字母在字符串后面,
	//以此为基准匹配出来的字符串的开头一定是前面的字符(除非是单字符),无法保证是否被使用过 
}
void work()
{
	dp[0][0]=0;
	for(int i=1;i<=k;i++) dp[i][i]=dp[i-1][i-1]+sum[i][i];//初始化,前k个字母每个作为一段 
	for(int i=1;i<=m;i++) dp[i][1]=sum[1][i];//初始化不划分的情况(分成1段) 
	
	for(int i=1;i<=m;i++)
	for(int j=1;j<=k&&j<i;j++) 
	//j<=k是因为最多分成k段,而j<i是因为j=i已经在第一个初始化中搞定了,当然逻辑上j<=i也没问题
	for(int l=j-1;l<i;l++) 
	//我觉得l其实应该等于j-1而不是j,因为下面的dp[l][j-1]在l=j-1时
	//是符合要求的,j-1个字符分成j-1段没有问题,当然两个都能AC,我就是说一下我的看法 
	dp[i][j]=max(dp[i][j],dp[l][j-1]+sum[l+1][i]);
	//l代表新放置的隔断位置,也可以理解为原来分成j段的倒数第二段的最后一个字符,
	//因为l+1就代表最后一段的第一个字符;因此 dp[l][j-1]+sum[l+1][i]的意思就是1到l的位置分成j-1段后的 
	//最多单词数加上剩余的一段(即l+1到 i)的最多单词数 
}
int main()
{
	init();
	work();
	cout<<dp[m][k];
	return 0;	
} 

work函数

work函数也可以写成这样:

void work()
{
	dp[0][0]=0;
	
	for(int i=1;i<=m;i++)
	for(int j=1;j<=k&&j<=i;j++) 
	//j<=k是因为最多分成k段
	for(int l=j-1;l<i;l++) 
	//我觉得l其实应该等于j-1而不是j,因为下面的dp[l][j-1]在l=j-1时
	//是符合要求的,j-1个字符分成j-1段没有问题,当然两个都能AC,我就是说一下我的看法 
	dp[i][j]=max(dp[i][j],dp[l][j-1]+sum[l+1][i]);
	//l代表新放置的隔断位置,也可以理解为原来分成j段的倒数第二段的最后一个字符,
	//因为l+1就代表最后一段的第一个字符;因此 dp[l][j-1]+sum[l+1][i]的意思就是1到l的位置分成j-1段后的 
	//最多单词数加上剩余的一段(即l+1到 i)的最多单词数 
}

无注释AC代码

#include<bits/stdc++.h>
using namespace std;
int p,k,m,n,dp[201][201],sum[201][201];
string s,str[10];
inline int read()
{
	int x=0,ch=1;
	char c=0;
	while(c<'0'||c>'9') {
		if(c=='-') ch=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9') {
		x=x*10+c-'0';
		c=getchar();
	}
	return ch*x;
}
bool check(int l,int r)
{
	string t=s.substr(l,r-l+1);
	for(int i=1;i<=n;i++){
		if(t.find(str[i])==0) return true;
	}
	return false;
}
void init()
{
	p=read(),k=read();
	s+='0';
	string t;
	for(int i=1;i<=p;i++)
	{
		cin>>t;
		s+=t;
	}
	n=read(),m=s.length()-1;
	for(int i=1;i<=n;i++) cin>>str[i];
	for(int i=m;i>=1;i--)
	for(int j=i;j>=1;j--)
	{
		sum[j][i]=sum[j+1][i];
		if(check(j,i)) sum[j][i]++;
	}
}
void work()
{
	for(int i=1;i<=m;i++) 
	for(int j=1;j<=k&&j<=i;j++)
	for(int l=j-1;l<i;l++)
		dp[i][j]=max(dp[i][j],dp[l][j-1]+sum[l+1][i]);
	cout<<dp[m][k];
}
int main()
{
	init();
	work();
}

参考

题解 P1026 【统计单词个数】
noip提高2001 统计单词个数:STL字符串类型及函数使用:dp
题解 P1026 【统计单词个数】

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
该资源内项目源码是个人的课程设计、毕业设计,代码都测试ok,都是运行成功后才上传资源,答辩评审平均分达到96分,放心下载使用! ## 项目备注 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.md文件(如有),仅供学习参考, 切勿用于商业用途。 该资源内项目源码是个人的课程设计,代码都测试ok,都是运行成功后才上传资源,答辩评审平均分达到96分,放心下载使用! ## 项目备注 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.md文件(如有),仅供学习参考, 切勿用于商业用途。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值