nowcoder NC17137 - Removal(dp)

题目链接:https://ac.nowcoder.com/acm/problem/17137

题意:给你 n n n个数( 1 ≤ a i ≤ k 1\le a_i \le k 1aik),问你删掉 m m m个数以后得到的不同的子序列的个数对 1 e 9 + 7 1e9+7 1e9+7取模的结果。

思路:首先我们可以考虑一个字问题:

  1. 给你 n n n个数( 1 ≤ a i ≤ k 1\le a_i \le k 1aik),问你删掉 m m m个数以后得到的子序列的个数(包括重复的子序列)对 1 e 9 + 7 1e9+7 1e9+7取模的结果。

拿着就是一道很简单的 d p dp dp问题啦,我们设 d p [ i ] [ j ] dp[i][j] dp[i][j]表示前 i i i个数里删掉 j j j个数得到的子序列的个数,那么就有
d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + d p [ i − 1 ] [ j − 1 ] dp[i][j] = dp[i-1][j] + dp[i-1][j-1] dp[i][j]=dp[i1][j]+dp[i1][j1]
之后我们在考虑如何删掉重复的子序列。
我们可以用一个数组 p r e [ ] pre[] pre[]去记录第 i i i个数他前面的第一个相同的数所在的位置。
然后我们在考虑一个子问题:

  1. 就是在不考虑长度的情况下子序列重合的个数。

那很明显就是 d p [ p r e [ i ] − 1 ] dp[pre[i]-1] dp[pre[i]1]
那现在考虑删掉个数的情况呢?
假设我们现在已经删掉了 j j j个数,那么 d p [ p r e [ i ] − 1 ] [ j − ( i − p r e [ i ] ) ] dp[pre[i]-1][j - (i-pre[i])] dp[pre[i]1][j(ipre[i])]是不是就是重合的方案数呢?因为我们要把 i − p r e [ i ] i-pre[i] ipre[i]之间的数全部删掉才行呀!!!

比如

2 3 4 1 5 6 1

对于这组样例,重复的序列是

2 3 4 1

就是只有把两个1之间的5 6删掉才有重复的序列。
那这一题就结束啦。

AC代码如下:

#include <set>
#include <map>
#include <stack>
#include <queue>
#include <cmath>
#include <ctime>
#include <vector>
#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

#define LL long long
#define pii pair<int,int>
#define sd(x) scanf("%d",&x)
#define slld(x) scanf("%lld",&x)
#define pd(x) printf("%d\n",x)
#define plld(x) printf("%lld\n",x)
#define rep(i,a,b) for(int i = (a) ; i <= (b) ; i++)
#define per(i,a,b) for(int i = (a) ; i >= (b) ; i--)
#define mem(a) memset(a,0,sizeof(a))
#define lson l , m , rt << 1
#define rson m + 1 , r , rt << 1 | 1
#define fast_io ios::sync_with_stdio(false)

const int INF = 1e9 + 10;
const LL mod = 1e9 + 7;
const int maxn = 1e5 + 7;

inline int read() {
    char c = getchar();
    int x = 0, f = 1;
    while (c < '0' || c > '9') {
        if (c == '-') f = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9') {
        x = x * 10 + (c - '0');
        c = getchar();
    }
    return x * f;
}

int head[maxn << 1];
struct Edge {
    int to, next;
}edge[maxn << 1];
int tot;

void init() {
    memset(head, -1, sizeof(head));
    tot = 0;
}

void addedge(int u, int v) {
    edge[tot].to = v;
    edge[tot].next = head[u];
    head[u] = tot++;
}

LL qpow(LL a, LL b, LL p) {
    LL res = 1;
    while (b) {
        if (b & 1) res = (res * a) % p;
        a = (a * a) % p;
        b >>= 1;
    }
    return res;
}

LL dp[maxn][20];
LL pre[maxn];
LL c[maxn];
LL a[maxn];

int main() {
    int n,m,k;
    while(~scanf("%d%d%d",&n,&m,&k)) {
        mem(pre), mem(c);
        rep(i,1,n) {
            slld(a[i]);
            pre[i] = c[a[i]];
            c[a[i]] = i;
        }
        rep(i,0,n) dp[i][i] = dp[i][0] = 1;

        rep(i,1,n) {
            rep(j,1,m) {
                dp[i][j] = (dp[i-1][j] + dp[i-1][j-1]) % mod;
                if(pre[i] > 0 && j >= i - pre[i]) {
                    dp[i][j] = (dp[i][j] - dp[pre[i]-1][j-(i-pre[i])] + mod) % mod;
                }
            }
        }
        plld(dp[n][m]);
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值