2021寒假集训.2018牛客多校第一场E.Removal[DP][序列去重]

Bobo has a sequence of integers s1,s2,⋯,sns1,s2,⋯,sn where 1≤si≤k1≤si≤k.

Find out the number of distinct sequences modulo (109+7)(109+7) after removing exactly mm elements.

Input

The input consists of several test cases and is terminated by end-of-file.

The first line of each test case contains three integers nn, mm and kk.

The second line contains nn integers s1,s2,⋯,sns1,s2,⋯,sn.

* 1≤n≤1051≤n≤105

* 1≤m≤min{n−1,10}1≤m≤min{n−1,10}

* 1≤k≤101≤k≤10

* 1≤si≤k1≤si≤k

* The sum of nn does not exceed 106106.

Output

For each test case, print an integer which denotes the result.

Example

Input

3 2 2
1 2 1
4 2 2
1 2 1 2

Output

2
4

题意:
n 个数的序列,删除 m 个数后,剩余的数有多少种不同的序列组合

参考:https://blog.csdn.net/qq_35975367/article/details/105864187

题解:

dp问题
dp[i][j]表示前i个数字删去j个数字之后有多少个不同的序列数
列出转移方程:f [ i ] [ j ] = f [ i - 1 ] [ j ] + f [ i - 1 ] [j - 1]
转移方程很经典,就是我们考虑当前这个数删不删,如果不删,那就是前i-1个数删去j个数,如果删去,那就是前i-1个数删去j-1个数
但是!但是!
这个题会出现重复情况
比如:513241
删去四个数,有可能是51也可能是51(*为被删去的数)
,这不就重复了
怎么去除重复?
发生重复说明当前这个第i位的w选上了,我们需要找上一个出现的w,如果以w结尾并且子串的长度和删后长度(i-j)相等的就是和dp[ i ] [ j ] 重复的,直接减去即可.
为什么呢?继续看我给的样例:… 5 1 3 2 4 1
先不管5之前的省略号,假设5是第一位
我们知道第二位和第六位都是1,两个i之间的距离是len=5(含两端),如果我们将两个1之间的数全部删去,就是5 1 * * * 1,再删除任何一个1,剩下的数就是重复的,你删去前面的1,剩下5 * * * * 1,删除后面剩下5 1 * * * * 。所以重复的部分就是第一个1前面的数与1所组成的序列 。
加上省略号,5之前还有很多数,查重的话就是把省略号中(算上5)的方法数去掉
我们要用到:last[i]表示第i位的数w的上一次出现位置
能得到:
dp [ i ] [ j ] = d p [ i ] [ j ]− dp [ pre [ i ] − 1 ] [ j− ( i − pre [ i ] ) ]
dp [ pre [ i ] − 1 ] [ j− ( i − pre [ i ] ) ] :就是重复情况
pre[i] - 1就是重复的数字上一次出现的位置的前一位
(i - pre [ i ] )就是len-1,就是两个数之间的数全删去再加上删任意一个端点。
j-(len-1)就是把这些数删去后,还要删的数量,而要删的就是pre[i]-1之前的数
我可能讲的不是很明白,看代码吧

#include <bits/stdc++.h> 
using namespace std;
const int maxn = 1e5 + 2;
const int mod= 1e9+7;
int dp[maxn][12];
int a[maxn];
int pos[12], last[maxn];
void init()
{
	        for (int i = 0; i <= n; i++)
            dp[i][0] = dp[i][i] = 1;
}
int main() 
{
    int n, m, k,w;
    while (~scanf("%d%d%d",&n,&m,&k))
	{
        
		init();//初始化,不删去和全删去的都只有一种 
		memset(pos, 0, sizeof(pos));
        for (int i = 1; i <= n; i++) 
		{
            scanf("%d",&a[i]);
            last[i] = pos[a[i]];
            pos[a[i]] = i;
        }

        for (int i = 1; i <= n; i++) 
		{
             w = min(i - 1, m);
            for (int j = 1; j <= w; j++)
			{
                dp[i][j] = (dp[i - 1][j - 1] + dp[i - 1][j]) % mod;
                //不查重的情况 
                if (last[i] && i - last[i] <= j)//如果前面有重复的数字,并且所要删除的数字的数量要能够删去重复数字之间的数(加上一端重复的数)
                    dp[i][j] = (dp[i][j] - dp[last[i] - 1][j - (i - last[i])] + mod) % mod;
                //将重复部分去掉
            }
        }
        cout << dp[n][m] << endl;
    }
    return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值