牛客练习赛60 C-操作集锦[dp]

牛客练习赛60 C-操作集锦[dp]

题目链接:https://ac.nowcoder.com/acm/contest/4853/C

时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld

题目描述

有一款自走棋有26种操作,每种操作我们都用a,b,c,d,…,x,y,z的符号来代替.
现在牛牛有一个长度为n的操作序列,他现在可以从里面拿出某些操作来组合成一个操作视频, 比如说操作序列是abcd,那么操作视频就有a,b,c,d,ab,ac,ad等(也就是操作序列的子序列).他现在想知道长度为k且本质不同的操作视频有多少种.
比如对于abab,长度为2且本质不同的结果有ab,aa,ba,bb。
考虑到答案可能非常大,你只需要输出在模1e9+7意义下的答案就可以了.

输入描述

第一行两个整数n,k.
第二行一个长度为n的字符串,保证只存在小写字母.

输出描述

一行一个整数表示长度为k且本质不同的操作视频的个数.

样例输入

3 1
abc

样例输出

3

备注

1≤ n ≤1e3
0≤ k ≤n

分析

其实题目辣么长,说白了也就是:找出给定序列中的不重复定长子序列个数
(题目是操作集锦,为什么正文就变成操作视频了,盲猜机翻)
好吧,我承认有那么一瞬(把子序列看成了字串),我觉得只要把所有定长字串放入vector,然后unique一下就完事了,敲到一半,蓦然回首,突然发现是子序列,共有Ckn个,这复杂度不是开玩笑的,完全没有机会把子序列哪怕扫一遍。

真·分析

在这种情况下,我能想到的只有dp了,用动态规划来减少无必要的扫描,降低复时间杂度。
令dp[i]表示前i个字符中,定长子序列个数,pre[i]表示字符i上一次出现的下标,str储存字符串。
两种情况,构建状态转移方程:
①若pre[str[i]]==0,即前i-1个字符未出现过str[i]
dp[i]=dp[i-1]+m[k-1];
这里的m[k-1]代表 前i-1个字符中长度为k-1的子序列个数,这些序列接上str[i]组成新的长度为k的子序列。
②若pre[str[i]]!=0,即前i-1个字符出现过str[i]
dp[i]=dp[i-1]+m[k-1]-g[k-1];
这里的g[k-1]表示 前pre[str[i]]-1 (str[i]上一次出现位置的前一个位置)个字符中长度为k-1的子序列的个数,这些子序列后接str[i] 和 后接 str[pre[str[i]]]构成的子序列相同,所以要去重。

那么问题来了,这里的m,g怎么求呢?
其实仔细观察便会发现,这里的状态转移方程其实是递归定义的,m[i],g[i]其实本质与dp[i]相同,只不过k变成了k-1而已,那么求k-1又需要k-2…,但我们最好不要真的写成递归,容易重复计算以及爆栈,众所周知,递归可以改写成循环,那我们不妨从长度1开始,一步一步地向k迭代。

那么,空间复杂度需要增加一维,dp[i][j]代表前 j 个字符中长度为 i 的子序列个数。
完整的状态转移方程如下:
①若pre[str[i]]==0,即前i-1个字符未出现过str[i]
dp[i][j] = dp[i - 1][j - 1] + dp[i][j - 1];
②若pre[str[i]]!=0,即前i-1个字符出现过str[i]
dp[i][j] = dp[i - 1][j - 1] + dp[i][j - 1] - dp[i-1][pre[str[j]]-1];

需要注意的是取模问题,由于存在减法,可能出现负数,所以要先加上mod再取模
比如(12-9)%10==(12%10-9%10+10)%10

"Talk is Cheap. Show me the Code."

#include<stdio.h>
#include<stdlib.h>
#include<algorithm>
#include<math.h>
#include<string>
#include<vector>
#include<string.h>
using namespace std;
const int maxn = 1e3 + 5;
const int mod = 1e9+7;
typedef long long ll;
char str[maxn];
ll dp[maxn][maxn];
int pre[256];
int main(void)
{
    int n, k;
    scanf("%d %d", &n, &k);
    scanf("%s", str+1);
    for (int i = 1; i <= n; i++) {//预处理出长度为1的情况
        if (pre[str[i]])
            dp[1][i] = dp[1][i - 1];
        else
            dp[1][i] = dp[1][i - 1] + 1;
        pre[str[i]] = i;
    }
    for (int i = 2; i <= k; i++) {
        memset(pre, 0, sizeof(pre));
        for (int j = 2; j <= n; j++) {
            if (pre[str[j]] == 0)
                dp[i][j] = dp[i - 1][j - 1] + dp[i][j - 1];
            else
                dp[i][j] = dp[i - 1][j - 1] + dp[i][j - 1] - dp[i-1][pre[str[j]]-1] + mod;
            dp[i][j] %= mod;
            pre[str[j]] = j;
        }
    }
    if (k)
        printf("%lld\n", dp[k][n]%mod);
    else
        printf("1\n");//特判k==0
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章