[10.18模拟赛] 序列 (DP)

时间:2022-12-29 10:13:35

[10.18模拟赛] 序列

题目描述

山山有一个整数序列s1,s2,…,sn,其中1≤si≤k。

求出有多少个准确移除m个元素后不同的序列。答案模(1e9+7)

输入

输入包括几个测试用例,并且由文件结束终止。

每一个测试用例的第一行包含整数n,m和k。

第二行包含n个整数,即s1,s2,…,sn。

输出

对于每一个测试用例,输出一个表示结果的整数。

样例输入

3 2 2

1 2 1

4 2 2

1 2 1 2

样例输出

2

4

提示

\(1 ≤ n ≤ 10^5;1 ≤ m ≤ min{n - 1, 10}; 1 ≤ k ≤ 10; 1 ≤ si ≤ k;\)

n的总和不超过\(10^6.\)

Solution

其实这是牛客网暑假集训的题目

官方题解写的太飘逸~如下

预处理 Next[i][c]表示 i 之后第一个字符 c 的位置
dp[i][j]表示当时匹配到 i,删了 j 个,不同的方案数
转移时枚举下一个字符 c,转移到 dp[next[i][c],j+next[i][c]-i]

下面来讲一下蒟蒻我的理解,首先如果不考考虑删除重复元素的情况,那这道题其实还是比较水的,就是一个dp,设dp[i][j]为前i个元素删除了j个方案数

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

这个方程还是挺好理解的吧,这道题就建立在这个方程之上

我们看这样一个序列{...,4,3,1,4,5},如果删除中间3,1,4}三个元素,集合会变成这样{...4,3,1,4,5},同样如果删除前面{4,3,1}三个元素,集合变成了这样{...4,3,1,4,5},可以看到同样的集合我们算了两遍

那么怎么减去多余的部分呢?

我们记录一下每个元素x上一次出现的位置\(pre[x]\),出现的前一段{...}我们都是会重复计算的,所以要减去\(dp[pre[x]-1][j-i+pre[x]]\)

Code

#include<bits/stdc++.h>
#define rg register
#define il inline
#define Min(a,b) (a)<(b)?(a):(b)
#define Max(a,b) (a)>(b)?(a):(b)
#define lol long long
#define in(i) (i=read())
using namespace std; const int N = 1e5+10, M=20 , mod=1e9+7; int read() {
int ans=0,f=1; char i=getchar();
while(i<'0' || i>'9') {if(i=='-') f=-1; i=getchar();}
while(i>='0' && i<='9') ans=(ans<<1)+(ans<<3)+i-'0',i=getchar();
return ans*=f;
} int n,m,k;
int dp[N][M],pre[M]; int main()
{
//freopen("removal.in" , "r" , stdin);
//freopen("removal.out" , "w" , stdout);
while(scanf("%d%d%d",&n , &m , &k)!=EOF) {
memset(dp , 0 , sizeof(dp));
memset(pre , 0 , sizeof(pre));
for(int i = 0 ; i <= n ; i++) dp[i][0] = 1;
for(int i = 1 , x ; i <= n ; i++) {
in(x);
for(int j = 1 ; j <= (Min(i , m)) ; j++) {
dp[i][j] = (dp[i - 1][j - 1] + dp[i - 1][j])%mod;
if( pre[x] && (j >= i - pre[x]) )
(dp[i][j] -= dp[pre[x] - 1][j- i + pre[x]])%=mod;
}pre[x] = i;
}printf("%d\n",(dp[n][m]+mod)%mod);
}
}