程序设计思维week15 选做题

ZJM与生日礼物

ZJM 收到了 Q老师 送来的生日礼物,但是被 Q老师 加密了。只有 ZJM 能够回答对 Q老师 的问题,Q老师 才会把密码告诉 ZJM。
Q老师 给了 ZJM 一些仅有 01 组成的二进制编码串, 他问 ZJM:是否存在一个串是另一个串的前缀.
Input
多组数据。每组数据中包含多个仅有01组成的字符串,以一个9作为该组数据结束的标志。
Output
对于第 k 组数据(从1开始标号),如果不存在一个字符串使另一个的前缀,输出"Set k is immediately decodable",否则输出"Set k is not immediately decodable"。

每组数据的输出单独一行
Sample Input
01

10

0010

0000

9

01

10

010

0000

9
Sample Output
Set 1 is immediately decodable

Set 2 is not immediately decodable
解题思路(垃圾话)
字典树我以前也是学过的,但是学了之后就没怎么用过,因为打比赛遇到字符串题一旦不是签到题就等于没有这题了(还是太菜了)
这道题直接问的就是前缀问题,有两种情况。
①是否已经存在当前串的前缀。因为每次往树上插入都存了终点标记,所以在跑当前串时看看有没有这个标记就行了。
②当前串是否为已经存在的串的前缀。在插入当前串时判断最后一个字母是否已经存在就可以了。

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#include <algorithm>
#include <map>
#define ll long long
const int maxn = 100005;
using namespace std;
struct trie
{
    static const int N = 1010,charset = 2;
    int tot,root,child[N][charset],flag[N];
    trie()
    {
        memset(child,-1, sizeof(child));
        root = tot = 0;
    }
    void clear()
    {
        memset(child,-1, sizeof(child));
        root = tot = 0;
    }
    bool insert(char *str)
    {
        int now = root,len = strlen(str);
        for(int i=0;i<len;i++)
        {
            int x = str[i] - '0';
            if(child[now][x]==-1)
            {
                child[now][x] = ++tot;
                flag[now] = 0;
            }
            else if(i==len-1 || flag[child[now][x]])return true;
            now = child[now][x];
        }
        flag[now] = 1;
        return false;
    }
};
char s[maxn];
int main()
{
    int cnt = 0;
    bool vis = false;
    trie trietree;
    while (scanf("%s",s)!=EOF)
    {
        if(s[0]=='9')
        {
            cnt++;
            printf("Set %d is ",cnt);
            if(vis)printf("not ");
            printf("immediately decodable\n");
            vis = false;
            trietree.clear();
        }
        else
        {
            vis = trietree.insert(s);
        }
    }
    return 0;
}

ZJM与纸条

ZJM 的女朋友是一个书法家,喜欢写一些好看的英文书法。有一天 ZJM 拿到了她写的纸条,纸条上的字暗示了 ZJM 的女朋友 想给 ZJM 送生日礼物。ZJM 想知道自己收到的礼物是不是就是她送的,于是想看看自己收到的礼物在纸条中出现了多少次。

Input
第一行输入一个整数代表数据的组数

每组数据第一行一个字符串 P 代表 ZJM 想要的礼物, 包含英语字符 {‘A’, ‘B’, ‘C’, …, ‘Z’}, 并且字符串长度满足 1 ≤ |P| ≤ 10,000 (|P| 代表字符串 P 的长度).
接下来一行一个字符串 S 代表 ZJM 女朋友的纸条, 也包含英语字符 {‘A’, ‘B’, ‘C’, …, ‘Z’}, 满足 |P| ≤ |S| ≤ 1,000,000.

Output
输出一行一个整数代表 P 在 S中出现的次数.
Sample Input
3
BAPC
BAPC
AZA
AZAZAZA
VERDI
AVERDXIVYERDIAN
Sample Output
1
3
0
KMP字符串匹配问题。以前也学过KMP,但是很久不用也忘了,再学一次没差。
核心思想是当出现s与p失配的情况后利用nxtpos[]数组实现只移动p而不移动s上的位置指针i,所谓nxtpos数组,nxtpos[i]表示在字符串p上的位置i之前(不包括i)的字串中最大公共前缀后缀的长度(如p=ABCAB,nxtpos[4]=1,最大公共前缀后缀就是’A’。
求nxtpos的过程大概 就是个p与自己匹配的过程,个人理解,k是在完成一个不断跑前缀的任务,用来计算nxtpos[j],有一个小优化就是p[j]==p[k]的时候,由于原来的算法中k就是nxtpos[j],会导致s[i]与p[j]失配后还是会和p[k]失配,这时就继续递归使nxtpos[p]=nxtpos[k]。
然后nxtpos就求好了。
然后用nxtpos匹配,失配时让j=nxtpos[j]
真的很神奇

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#include <algorithm>
#include <map>
#define ll long long
const int maxn = 10005;
const int maxm = 1000005;
using namespace std;
int n;
int nxt[maxn];
char ptr[maxn],str[maxm];
void getnxt(const char s0[],int len)
{
    nxt[0] = 0;
    for(int i=1,j=0;i<len;i++)
    {
        while (j && s0[j] != s0[i])j = nxt[j-1];
        if(s0[j] == s0[i])j++;
        nxt[i] = j;
    }
}
int kmp()
{
    int len1 = strlen(str);
    int len2 = strlen(ptr);
    int j = 0;
    int ans = 0;
    getnxt(ptr,len2);
    for(int i=0;i<len1;i++)
    {
        while(j && ptr[j]!=str[i])j=nxt[j-1];
        if(ptr[j] == str[i])j++;
        if(j==len2)
        {
            ans++;
            j = nxt[j-1];//仔细看看那个套娃一样的样例,匹配完成要回到上一个搭配的位置才能保证不漏答案
        }
    }
    return ans;
}
int main()
{
    cin>>n;
    while (n--)
    {
        memset(ptr,0, sizeof(ptr));
        memset(str,0, sizeof(str));
        memset(nxt,0, sizeof(nxt));
        scanf("%s",ptr);
        scanf("%s",str);
        printf("%d\n",kmp());
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值