计蒜客Not a subsequence动态规划

题意:

In this problem we consider strings over a fixed finite alphabet of size k. The alphabet contains the first k characters from the list

a,b,c,…,z,A,B,C,…,Z,0,1,…,9.

For every test case, we are given the value of k(notice that it cannot exceed 62), and consider only strings consisting of the first k characters from the list.

Given a string s[1…n], we are interested in strings which are not its subsequences. Formally, a string t[1…m] is a subsequence of a string s[1…n] when one can choose not necessarily contiguous‾not necessarily contiguous indices 1≤i1<i2<…im≤n such that t[1]=s[i1],t[2]=s[i2],…,t[m]=t[im]

For example, acb is a subsequence of babcaabbabcaab. Now, given a string s[1…n], we would like to compute the smallest mm such that there is a string t[1…m], which is not a subsequence of s[1…n]. Additionally, we would like to count the number of such shortest strings t[1…m].

As the latter number can be quite large, output it modulo 10^9+7.

Input

The input starts with the number of test cases T≤100.

Then the descriptions of TT test cases follow. A single test case consists of a single line containing the size of the alphabet k(k∈[1,62]) and the string s【1…n】(n∈[1,10^6])]. The string consists of the first k characters from a−zA−Z0−9.

Output

For every test case output one line containing two numbers.

The first number is the smallest mm such that there is a string t[1…m] consisting of the first kkcharacters from a−zA−Z0−9, which is not a subsequence of s[1…n].

The second number is the total count of such shortest strings t[1…m] modulo 10^9+7.

样例输入
3
2 abba
62 0123456789
3 aabbcbbcbabcbab
样例输出
3 5
1 52
4 7

​ 翻译成汉语大概意思就是给你T组样例,每组样例有一个n,代表有n种字符,然后再给出一个字符串。问字符串中不存在的子串的最小长度是多少?有多少种(答案取余1e9+7)?例如 abba,对于字符a,b来说,它不存在的最小子串长度为3,分别为aaa,bbb,aab,baa,bbb

思路:

​ 一道特别难的dp题,关键是思路吧~

​ 我们首先考虑字符串中不存在的子串的最小长度,我们从前往后遍历,每当出现一个没有标记过的字符就标记一次,当n个字符都标记过时,此时子串长度为1的都有了,因此最小长度只能是2,此时我们需要清空标记,继续往后遍历,每当出现一个没有标记过的字符就标记一次,当n个字符都标记过时,此时子串长度为2的都有了(因为每种字符前面一定每种字符至少存在一次),因此最小长度只能是3

​ 我们再来考虑不存在的最小长度子串有多少种,若最小长度子串为1,那么种类为n-字符串中字符的种类数。若最小长度子串大于1时呢?这时候就需要DP推导式了…

​ 给大家举个例子吧:对于3 abbcbabc来说:

​ 从前往后遍历,第一个字符为a,没有标记过,然后标记该字符,此时字符计数num = 1。那么长度为2的以a结尾的子串没有出现的种类数为3(分别是aa,ba,ca),总计数sum[2] = 3。并设置字符a上一次出现的位置为1

​ 第二个字符b,没有标记过,然后标记该字符,此时字符计数num = 2。那么长度为2的以b结尾没有出现的子串种类数为2(分别是bb,cb),总计数sum[2] = 3 + 2 = 5。并设置字符b上一次出现的位置为2

​ 第三个字符b,标记过,此时字符计数num = 2。长度为2的以b结尾没有出现的子串种类数为1(cb),总计数sum[2] = 3 + 2 + 1 - 2 = 4(也就是sum[2] + 1 - 上次b出现加的2)。并设置字符b上一次出现的位置为3

​ 第四个字符c,没有标记过,然后标记该字符,此时字符计数num = 3。那么长度为2的以c结尾没有出现的子串种类数为1(分别是cc),总计数sum[2] = 3 + 2 + 1 - 2 + 1 = 5。并设置字符c上一次出现的位置为4,此时num==字符种类数,因此没出现的最小子串长度至少为2,清空标记与字符计数num

​ 第五个字符b,没有标记过,然后标记该字符,此时字符计数num = 1。那么长度为3的以b结尾没有出现的子串种类数为5(分别是aab,bab,cab,cbb,ccb),总计数sum[3] = sum[2] = 5,由于上一次出现过b且上次出现b是求长度为2的,因此sum[2] = sum[2] - 1 = 4(也就是sum[2]- 上一次b出现增加的1),并设置字符b上一次出现的位置为5

​ 第六个字符a,没有标记过,然后标记该字符,此时字符计数num = 2。那么长度为3的以a结尾没有出现的子串种类数为4(分别是aaa,baa,caa,cca),总计数sum[3] = 5 + 4 = 9,由于上一次出现过a且上次出现a是求长度为2的,因此sum[2] = sum[2] - 3 = 1(也就是sum[2]- 上一次a出现增加的3),并设置字符a上一次出现的位置为6

​ 第七个字符b,标记过,此时字符计数num = 2。那么长度为3的以b结尾没有出现的子串种类数为1(ccb),总计数sum[3] = 5 + 4 + 1 = 10,由于上一次出现过b且上次出现b是求长度为3的,因此sum[3] = sum[3] - 5 = 5(也就是sum[3]- 上一次b出现增加的5),并设置字符b上一次出现的位置为7

​ 第八个字符c,没有标记过,然后标记该字符,此时字符计数num = 3。那么长度为3的以c结尾没有出现的子串种类数为1(ccc),总计数sum[3] = 5 + 1 = 6,由于上一次出现过c且上次出现c是求长度为2的,因此sum[2] = sum[2] - 1 = 0(也就是sum[2]- 上一次c出现增加的1),并设置字符c上一次出现的位置为8,由于num == 字符种类数,因此没出现的最小子串长度至少为3

​ 因此最小子串长度为3,没出现的种类数为sum[3] = 6

​ 原理就是这个原理~只需要线性就能求解

代码:

#include <stdio.h>
#include <string.h>
#define N 1000000
#define mod 1000000007
#define ll long long
#define mem(a) memset(a, 0, sizeof(a))
char s[N + 5];		//存储字符串
int map[300];		//存储字符映射
bool book[70];		//标记数组
ll add[70];			//存储该字符上一次增加的值
int flag[70];		//记录该字符上一次是为了长度为几的子串贡献的add
ll dp[N + 5];		//dp[i]存储长度为i的子串没有出现的种类数

void init() {
    int tot = 0;
    for (int i = 'a'; i <= 'z'; i++) {
        map[i] = tot++;
    }
    for (int i = 'A'; i <= 'Z'; i++) {
        map[i] = tot++;
    }
    for (int i = '0'; i <= '9'; i++) {
        map[i] = tot++;
    }
    return;
}

int main() {
    init();
    int T, n;
    scanf("%d", &T);
    while (T--) {
        scanf("%d%s", &n, s);
        int len = strlen(s);
        mem(book);
        mem(dp);
        mem(flag);
        mem(add);
        dp[1] = n;
        int chang = 1, num = 0;
        for (int i = 0; i < len; i++) {
            int t = map[s[i]];
            if(chang == 1) {
                dp[2] = (dp[2] + n - num + mod) % mod;
                if (book[t]) {
                    dp[2] = (dp[2] - add[t] + mod) % mod;
                } else {
                    dp[1]--;
                }
                add[t] = n - num;
            } else {
                dp[chang + 1] = (dp[chang + 1] + dp[chang]) % mod;
                ll zj = dp[chang];
                if(book[t]) {
                    dp[chang + 1] = (dp[chang + 1] - add[t] + mod) % mod;
                } else {
                    if (flag[t] == chang - 1) {
                        dp[chang] = (dp[chang] - add[t] + mod) % mod;
                    }
                }
                add[t] = zj;
            }
            flag[t] = chang;
            if(!book[t]) {
                num++;
                book[t] = 1;
            }
            if (num == n) {
                chang++;
                mem(book);
                num = 0;
            }
        }
        printf("%d %lld\n", chang, dp[chang]);
    }
    return 0;
}

转载请注明出处!!!

如果有写的不对或者不全面的地方 可通过主页的联系方式进行指正,谢谢

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值