统计单词个数 (区间DP)

【问题描述】

给出一个长度不超过200的由小写英文字母组成的字母串(约定;该字串以每行20个字母的方式输入,且保证每行一定为20个)。要求将此字母串分成k份(1<k<=40),且每份中包含的单词个数加起来总数最大(每份中包含的单词可以部分重叠。当选用一个单词之后,其第一个字母不能再用。例如字符串this中可包含this和is,选用this之后就不能包含th)。
单词在给出的一个不超过6个单词的字典中。
要求输出最大的个数。

【输入文件】

去部输入数据放在文本文件input3.dat中,其格式如下:
每组的第一行有二个正整数(p,k)
p表示字串的行数;
k表示分为k个部分。
接下来的p行,每行均有20个字符。
再接下来有一个正整数s,表示字典中单词个数。(1<=s<=6)
接下来的s行,每行均有一个单词。

【输出文件】

结果输出至屏幕,每行一个整数,分别对应每组测试数据的相应结果。

【输入样例】

1 3
thisisabookyouareaoh

4

is

a

ok

sab

【输出样例】

7

【问题分析】

刚看到这个题目觉得很迷茫,没入手点但是突然看到了闪亮的突破口:题目中说this包含this和is 但不包含th这也就是说在一个串内对于一个固定了起点的单词只能用一次,即使他还可以构成别的单词但他还是用一次。比如:串:thisa

字典:this is th

串中有this  is  th这三个单词,但是对于this 和 th 只用一次,也就是说枚举一下构成单词的起点,只要以该起点的串中包含可以构成一个以该起点开头的单词,那么就说明这个串中多包含一个单词。

考虑到这里,就有点眉目了。

题目中要将串分K个部分也就是说从一个点截断后一个单词就未必可以构成了。比如上例要分3个部分合理的其中的一个部分至多有3个字母,这样this 这个单词就构不成了。

要是分5个部分,那就连一个单词都够不成了。

这样就需要对上面做个改动,上面的只控制了起点,而在题目中还需要限制终点,分完几个部分后,每部分终点不同可以构成的单词就不同了。

这样就需要再枚举终点了。

设计一个二维数组sum[i,j]统计从i到j的串中包含的单词的个数

状态转移方程:

 

sum[i+1,j]+1           (s[i,j]中包含以S[i]开头的单词)

sum[i,j]=

sum[i+1,j]            (与上面相反)

注:(1)这里枚举字符的起点的顺序是从尾到头的。

(2)有人把上面这次也看做是一次动态规划,但我觉得更准确的说是递推。

求出所有的SUM还差一步,就是不同的划分方法显然结果是不一样的,但是对于求解的问题我们可以这样把原问题分解成子问题:求把一个串分成K部分的最多单词个数可以看做是先把串的最后一部分分出来,在把前面一部分分解成K-1个部分,显然决策就是找到一种划分的方法是前面的K-1部分的单词+最后一部分的单词最多。

显然这个问题满足最优化原理,那满足不满足无后效性呢?

对于一个串分解出最后一部分在分解前面的那部分是更本就不会涉及分好的这部分,换句话说没次分解都回把串分解的更小,对于分解这个更小的传不会用到不属于这个小串的元素。这就满足无后效性。

具体求解过程:

设计一个状态opt[i,j]表示把从1到j的串分成i份可以得到最多的单词的个数。决策就是枚举分割点使当前这种分割方法可以获得最多的单词。

状态转移方程:opt[I,j]=max(opt[i-1,t]+sum[t+1,j])      (i<t<j)

边界条件:opt[1,i]=sum[1,i]                        (0<i<=L)

时间复杂度:状态数O(N2)*决策数O(N)=O(N3)


#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
using namespace std;

int p, k, c;
char s[300], d[7][300];
int sum[300][300], dp[300][50];

int check(int l, int r)
{
    for(int i = 0; i < c; i++)
    {
        int len = strlen(d[i]);
        if(0 == strncmp(d[i],s+l,len))
            return 1;
    }
    return 0;
}

int main()
{
    while(scanf("%d%d",&p,&k) != EOF)
    {
        int i, j, t;
        for(i = 0; i < p; i++)
            scanf("%s",s+i*20);
        scanf("%d",&c);
        for(i = 0; i < c; i++)
            scanf("%s",d[i]);

        int len = strlen(s);
        for(j = len - 1; j >= 0; j--)
        {
            sum[j][j] = check(j,j);
            for(i = j - 1; i >= 0; i--)
                sum[i][j] = sum[i+1][j] + check(i,j);
        }

        for(i = 0; i < len; i++)
            dp[i][1] = sum[0][i];
        for(t = 2; t <= k; t++)
            for(i = t - 1; i < len; i++)
                for(j = 0; j < i; j++)
                    dp[i][k] = max(dp[i][k], dp[j][k-1] + sum[j+1][i]);
        printf("%d\n",dp[len-1][k]);
    }
    return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值