洛谷 P1026 统计单词个数

题目描述
给出一个长度不超过 
200 的由小写英文字母组成的字母串(该字串以每行20 个字母的方式输入,且保证每行一定为20 个)。要求将此字母串分成 k 份,且每份中包含的单词个数加起来总数最大。
每份中包含的单词可以部分重叠。当选用一个单词之后,其第一个字母不能再用。例如字符串 this 中可包含 this 和 is,选用 this 之后就不能包含 th。
单词在给出的一个不超过 6 个单词的字典中。
要求输出最大的个数。
输入格式
每组的第一行有两个正整数 
p,k 
p 表示字串的行数,
k 表示分为k个部分。
接下来的 p 行,每行均有 20 个字符。
再接下来有一个正整数 s,表示字典中单词个数。 接下来的 s 行,每行均有一个单词。
输出格式
1个整数,分别对应每组测试数据的相应结果。
输入输出样例
输入 #1 
复制 
1 3
thisisabookyouareaoh
4
is
a
ok
sab
输出 #1 
复制 
7
说明/提示
【数据范围】
对于
100% 的数据,
2≤k≤40
1≤s≤6。
【样例解释】 划分方案为 this / isabookyoua / reaoh

经过了3天多的肝题,这题最终是被做出来了!我一开始觉得是哈希,在补习了一阵子后发现数据真的~真的~真的很小。模拟都能做出来哎。于是原来的哈希+dp被硬改成了模拟+dp。

模拟很好想对吧,定义一个二维数组,用来存一个区间的单词个数。计算值的时候固定的是结尾位置,为什么?因为好算,每次把开头往前一个的话,如果从开头位置能找到单词,也就不会有某个单词和他头部重叠,这样自然也就不用去想"第一个字母不能再用"这种东西。

好了贪心已经做完了,虽然只说了想法,但是有想法了,打代码不是很快么?

所以直接讲dp吧。

这个题目的dp是一个比较难的dp(只是因为我不会所以我觉得很难),应该是区间dp。就是把一个大区间的值看成是2个小区间的值加起来,然后用我们已知的区间的值来求后面的区间,这样一直求就可以求出目标区间。然后有了大体思想我们就可以去推(猜)转化方程了。

我们先审一下题:他要把一长串分成k段,那我们可以随便找一个点,把前面和后面的分成2段,这样我们需要求的就是前面的那一段分成k-1段加上最后一段的值。然后从这些值里选一个最大的就好了!理想很丰满显示很骨感……我推(猜)对了方程写错了代码QWQ。挂了2次……一个地方是分成k部分,我写成了分k次(就是分成k+1部分,因为我的for循环从0开始,啊悲剧),还有一个点是前面分的那一部分根本分不了k段,比如我要在长100的字符串分成k段,可能会出现第1个字符分成k-1次,后面99个不分的情况。所以…又炸了…

 

经过调整代码已经改好了,来看看成品的样子吧:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
long long p,k,n;
string s,zs="";
string s2;
string sz[10];
long long f[205][205];//f用来保存一个区间的字符串的个数 
long long cd[205];//名字很显然,长度 
long long dp[205][45];//dp[i][j]表示前i个数分成j份的和最大是多少 
string jq;
long long z;
bool hx(long long wz,long long wz2)//匹配新加入的开头可不可以当一个单词的开头 
{
	for(int i=0;i<n;i++)
	{
		if(wz+cd[i]-1>wz2)//超长了 
		{
			continue;
		}
		jq=zs.substr(wz,cd[i]);//这东西是截取字符串的,用法是substr(截取开始位置,包括开始位置一共截取长度)。 
		if(jq==sz[i])//可以对上!好了有一个单词可以匹配上了,直接结束。 
		{
			return true;
		}
	}
	return false;
}
int main()
{
	cin>>p>>k;
	for(int i=0;i<p;i++)
	{
		cin>>s;
		zs+=s;//zs是真正的字符串 
	}
	cin>>n;
	for(int i=0;i<n;i++)
	{
		cin>>sz[i];
		cd[i]=sz[i].length();//这个是长度,一会截取字符串要用 
	}
	for(int i=p*20-1;i>=0;i--)//上面讲过的模拟策略 
	{
		for(int j=i;j>=0;j--)
		{
			f[j][i]+=f[j+1][i];//后面的要继承上 
			if(hx(j,i)==true)//因为不能有开头重合的字符串,所以一个开头最多选一个,因此只要有一个匹配我们就选他就可以了。 
			{
				f[j][i]++;
			}
		}
	}
	for(int i=0;i<=p*20-1;i++)//i是开头 
	{
		for(int j=1;j<=k;j++)//j是分成j块(我之前就是把j初始值定义成0所以wa了一次。 
		{
			if(j==1)//这个值已经知道了,直接赋值 
			{
				dp[i][j]=f[0][i];
			}else
			{
				for(int u=j+1;u<i;u++)//开始枚举分割点 
				{
					dp[i][j]=max(dp[i][j],dp[u][j-1]+f[u+1][i]);//这一段要分裂成j块,可以看成从0到u的一大块分成j-1块,再加上u后面到i不分割,然后这个方程就出来了。 
				}	
			}
		}
	}
	cout<<dp[p*20-1][k]<<endl;//输出 完结撒花 
	return 0;
}

好了好了,这个题就算写完了吧,完结撒花。

posted @ 2020-07-17 09:06  lichangjian  阅读(384)  评论(0编辑  收藏  举报