[10.18模拟赛] 序列 (DP)

[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
",(dp[n][m]+mod)%mod);
    }
}
原文地址:https://www.cnblogs.com/real-l/p/9811421.html