码迷,mamicode.com
首页 > 其他好文 > 详细

JZOJ4316【NOIP2015模拟11.5】Isfind 题解

时间:2018-01-25 00:27:42      阅读:225      评论:0      收藏:0      [点我收藏+]

标签:判断   abc   子序列   for   并且   遍历   open   文章   组成   

JZOJ4316 【NOIP2015模拟11.5】Isfind 题解

Description

技术分享图片

Input

技术分享图片

Output

技术分享图片

Sample Input

    4 3
    acbc
    abc
    cba
    cc

 

Sample Output

    Y
    N
    Y

Data Constraint

技术分享图片

思路:

    题意要看懂,首先声明一下“子串”和“子序列”的区别,S的“子串”意思是在S中选取任意i个(0 < i <= |S|)连续字符组成的字符串,而S的“子序列”意为在S中任选i个字符,按原来的顺序连接而成的字符串。举个例子,对于字符串"abaacba","baac"是这个字符串的子串,"bca"不是这个字符串的子串而是这个字符串的子序列。

    那么看题,我们需要实现的算法就是判断一个字符串是否为另一个字符串的子序列。

    1.暴力算法,贪心思想。

    考虑遍历每次给出的字符串,如果给出的这个字符串是S的子序列,那么S必然包含这个字符串的每个字符,并且这些先后顺序与给出的字符串一致。对于每个给出的字符串T,遍历每个Ti,在S中找到从先到后的第一个Sj=Ti,并且满足j要在上一个匹配的字符后,如此循环往复,如果对于每个Ti都能找到对应的Si,那么T就是S的子序列,匹配成功,反之亦然。

    看文字看不明白?根据样例讲吧:

    将T=‘abc‘和S=‘acbc‘匹配,首先在S中找到‘a‘第一次出现的位置,位置为1,匹配成功,然后在S中找在1之后‘b‘第一次出现的位置,位置为3,匹配成功,然后在S中找在3之后‘c‘第一次出现的位置,位置为4,匹配成功,所有字符全部匹配。

    将T=‘cba‘和S=‘acbc‘匹配,首先在S中找到‘c‘第一次出现的位置,位置为2,匹配成功,然后在S中找在2之后‘b‘第一次出现的位置,位置为3,匹配成功,然后在S中找在3之后‘a‘第一次出现的位置,‘a‘在3之后没有出现过,匹配失败。

    要明白,每次之所以跳第一次出现的位置,是为了使S剩下可以匹配的字符尽量多,使尽量多的字符参加匹配。

    复杂度分析,对于每个T,其与S匹配时,最坏情况下S的每个字符都要参与遍历,时间复杂度为O(n),m次匹配的复杂度就是O(nm),超时。

    2.暴力之上的优化。

    观察复杂度式子O(nm),显然O(m)的复杂度是避免不了的,于是我们可以尝试在n上面做些文章。

    暴力过程中,我们一步一步跳S来找从位置i开始字母c第一次出现的位置,但是S与询问无关,每次询问都是与S匹配,S是独立于询问的,也就意味着如果S给出了,从位置i开始字母c第一次出现的位置就是固定的了。于是就能预处理搞了。

    设一个f[i][j]表示从i位置开始,j字母第一次出现的位置,易得转移方程为

    1.f[i][j] = i (s[i] == j)

    2.f[i][j] = f[i + 1][j] (s[i] != j)

    转移时i需倒序枚举,因为f[i][j]需要用到f[i + 1][j]的东西。

    复杂度的话,预处理复杂度为O(26n)常数可以省略即O(n),对于m次询问,每次询问只需将读入的字符串T遍历一遍,复杂度为O(|T|)总共这m次询问加起来就是所有T的长度之和,题目给出的总长度<=4*10^6,可以视为线性的复杂度,线性加线性,于是总复杂度可视为O(n)。

    听不懂的话可以根据代码理解哦:

    

 1 #include <cmath>
 2 #include <cstdio>
 3 #include <cstring>
 4 #include <cstdlib>
 5 
 6 const int N = 1e5 + 3, M = 4e6 + 7;
 7 
 8 int n, q, po, len;
 9 char str[M];
10 int f[N][26]; //注意这里不能开f[M][26],空间超限
11 
12 int main()
13 {
14     freopen("isfind.in", "r", stdin); //无视文件操作
15     freopen("isfind.out", "w", stdout); //无视文件操作
16 
17     memset(f, 0xff, sizeof(f)); //0xff经过memset以后就是0xffffffff即-a
18     scanf("%d%d%s", &n, &q, str + 1); //方便处理,从1开始
19     for (int i = n; i >= 1; i--) //倒序枚举
20         for (int j = 0; j < 26; j++)
21             if (j + a == str[i]) f[i][j] = i; //如上所述
22             else f[i][j] = f[i + 1][j];
23     while (q--)
24     {
25         scanf("%s", str + 1);
26         po = 1, len = strlen(str + 1); //从1开始匹配
27         for (int i = 1; i <= len; i++)
28         {
29             po = f[po][str[i] - a]; //找到第一个出现的位置
30             if (po == -1) //不匹配
31             {
32                 printf("N\n");
33                 break;
34             }
35             else po++; //移到下一位继续匹配
36         }
37         if (po != -1) printf("Y\n"); //匹配
38     }
39 
40     fclose(stdin);
41     fclose(stdout);
42     return 0;
43 }

 有问题请留言。

JZOJ4316【NOIP2015模拟11.5】Isfind 题解

标签:判断   abc   子序列   for   并且   遍历   open   文章   组成   

原文地址:https://www.cnblogs.com/zjlcnblogs/p/8343716.html

(0)
(1)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!