牛客网多校第一场 E Removal (DP)

题目链接:点击此处

题目大意:一个长度为n的数组,问其删除了m个字符后,所有不同的子串有多少个

首先我们来看看这个题目的简化版:

51 Nod 1202 子序列个数

题目很裸,就是问一个数组子序列的个数

我们用dp来解决这个问题,dp[i]表示前i个字符能够组成多少个不同的子序列

首先每次对于一个数的加入

如果这个数没有在前面出现过

如果我们选择这个数,显然这个数可以和前面所有的子序列组成一个新的子序列

所以dp[i]=2*dp[i-1]

如果这个数在前面出现过,首先我们找到距离其最近出现的位置pos

对于选择了这个数的情况

比如:1 2 3 4 6 7 5

这个时候5不能和前面所有子序列组成一个新的子序列

对于pos位置之前所有的不同的子序列,和这个数字组成的所有子序列都已经通过pos位置那个相同的数算了一遍

所以我们这里要减去重复的数

dp[i]=2*dp[i]-dp[pos]

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long llt;
const int MOD=1e9+7;
const int MAXN=111111;
int arr[MAXN];
int last[MAXN];
llt dp[MAXN];
int main()
{
    int n;
    while(scanf("%d",&n)!=EOF)
    {
        memset(last,0,sizeof(dp));
        for(int i=1;i<=n;i++)
            scanf("%d",&arr[i]);
        dp[0]=1;
        for(int i=1;i<=n;i++){
            int val=arr[i];
            if(last[val]){
                dp[i]=(2*dp[i-1]-dp[last[val]-1]+MOD)%MOD;
            }else dp[i]=2*dp[i-1]%MOD;
            last[val]=i;
        }
        printf("%lld\n",dp[n]-1);
    }
    return 0;
}

然后我们回到正题,对于这个题目来说,解题思路和上面那个题目差不多,但是这个题目多了一个长度的限制

我们用dp[i][j]表示前i个数字,删除了j个字符,最多能组成多少个不同的子序列

情况和前面类似,如果这个数字没有在前面出现过,那么对于这个数字我们可以选择删或者不删

dp[i][j]=dp[i-1][j-1]+dp[i-1]][j]

如果这个数字在前面出现过,那么我们找到距离其最近的位置

首先因为dp[i][j]表示在前i个字符串删除了j个字符,也就是说取了(i-j)个数

如果我们这里选择了这个数,也就是说在前i-1个位置里面取了(i-1-j)个数

这里和之重复的部分是选择了这个字符出现的上一个位置,并且在前(last[val]-1)个位置选择了(i-1-j)个数

也就是说在前(last[val]-1)个数中删除了(last[val]-1)-(i-1-j)个数

所以这里dp[i][j]=dp[i-1][j-1]+dp[i-1]][j]+dp[last[val]-1][(last[val]-1)-(i-1-j)]

附上代码~~

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
//题目问的是长度为k的字符串删除了k个子串后
//存在多少个互不相同的子序列
typedef long long llt;
const int MOD=1e9+7;
const int SIZE=1e5+111;
int arr[SIZE];
llt dp[SIZE][11];
int last[15];
int main()
{
    int n,m,z;
    while(scanf("%d%d%d",&n,&m,&z)!=EOF)
    {
        memset(last,0,sizeof(last));
        for(int i=1;i<=n;i++)
            scanf("%d",&arr[i]);
        //无论长度如何,如果一个数字都没删除的子序列有且仅有一种
        for(int i=0;i<=n;i++) dp[i][0]=1;
        for(int i=1;i<=n;i++){
            int val=arr[i];
            for(int j=1;j<=m;j++){
                if(!last[val]) dp[i][j]=(dp[i-1][j]+dp[i-1][j-1])%MOD;
                else{
                    //因为前面出现了这个数,所以我们要去掉重复的
                    //首先这个地方长度应该已经取了
                    int pos=(i-j-1);
                    int dl=last[val]-pos-1;
                    if(dl>=0) dp[i][j]=(dp[i-1][j]+dp[i-1][j-1]-dp[last[val]-1][dl]+MOD)%MOD;
                    else dp[i][j]=(dp[i-1][j]+dp[i-1][j-1])%MOD;
                }
            }
            last[val]=i;
        }
        printf("%lld\n",dp[n][m]);
    }
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值