题目链接: http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3430
这道题的难点在于base64转十进制的问题,这里我先讲几个细节;
首先是段错误的问题;在我做过的oj里面,就只有ZOJ会有段错误的问题;会出现段错误主要(在这道题上)问题有两个;第一个就是内存大了,我总感觉ZOJ在内存上真的有点小气,数组稍微开大了一点就会出现段错误,而不是说内存超限,我之前做过一道题能在POJ上AC,并且内存也是在要求范围之内的,但是在zoj却会出现段错误的问题,这里开数组的时候,尽量精确一点点,不要开多了;第二就是一个字符ascall码的内存访问问题,在base64转十进制的规则里面,是让我们在一个以6个字节为单位的二进制长串中逐个取8个字节来组成一个十进制的字符的,比如说 100100 | 100100 这里有两个二进制的base64字符,我们从左往右取,取8个作为一个十进制的字符,但是这里会出现一个问题就是ascall码访问超出范围了,为什么? 一般情况下,ascall码的十进制序号是0到127,而127 只有7个字节,也就是二进制1111111,如果你取8个字节,那这个ascall码肯定是超出范围的,在zoj就会出现段错误;第一个问题很好解决,那第二个怎么解决? 如果我们取8个字节,那么取出的这8个字节组成的十进制数最大是 256,那么8字节以内能够组成的数就有0~256,而且每一个数字都代表着一个字符,事实上,我们并不需要真的把base64字符串转换成一个可读的十进制字符串,就算是数字也不影响我们用AC自动机去匹配字符,仔细想想,是不是这么一回事?比如说 base64的字符 0000(一个字符0在base64中的ascall码是52),对应的二进制就是 110100 | 110100 | 110100 | 110100 我们从左边逐个取8个 11010011 (211) 01001101(77) 00110100(52)那么这个时候就有3个十进制的字符,因为每8个二进制数代表的数字都是唯一的,所以,我们并不需要把它们都翻译成实际意义的字符,直接把对应的数字带进Trie树中去做AC自动机的匹配(AC自动机不仅能匹配字符,也能把数字当字符匹配),这样我们就根本不用去管ascall会不会超出访问范围了;
还剩最后一个问题就是如何把base64转换成对应的十进制数;
大致的思路是这样的:
因为我们要从base64字符的二进制串中逐个取8个字节,我们可以先访问base64字符串的第一个字符,用一个j标记一下当前字符串有几个字节,每带进一个字符就+6,因为一个base64字符就6个字节嘛,判断当前的 j 够不够8个,够的话就取前8个,然后j - 8,然后继续引进下一个字符;
可能有些人不太熟悉在一个进制数中取自己想要的进制数,这里有两个需要知道的,我们要取 101010 这个二进制数的前 4个进制数怎么取? x = ((101010)>> 2 ) & 001111 ,应该这样取, 步骤是这样的: 101010 左移 2位就是 (00) 1010 (括号里面的0是补位的,你向左移几位,右边就补几个0),第二步 00 1010 & 00 1111 = 001010 (对位运算不熟悉的建议先去找一些资料学习一下,这里就不解释了),取后4个也是一样的道理;还有一个操作就是怎么把前一个二进制数的后两个和后一个二进制数的前4个和在一起? x = (101010 & 000011 ) | ( (101010 >> 2) & 001111) ,自己在纸上演算一下应该就能明白了;
这个博客有讲base64转十进制的规则,可以去看看:
https://blog.youkuaiyun.com/cyh706510441/article/details/49048755
emm,差不多就这些了吧,下面给出代码(代码是次要的,自己还是要去尝试着写一写,根据上面提示的一些思路);
#include<iostream>
#include<cstring>
#include<string>
#include<queue>
#include<cstdio>
using namespace std;
const int Maxn = 10005;
const int maxn = 50005;
int vis[maxn]; // 因为要匹配的不止一个主字符串,而且之要求输出病毒种类有几个,
// 为了不破坏Trie树,用vis来判断是否已经走过这个节点,只要初始化一次就能重复用
struct Trie {
int node;
int child[maxn][266],fail[maxn],val[maxn];
void init () {
node = -1;
newnode ();
}
int newnode () {
memset(child[++node],0,sizeof(child[0]));
fail[node] = val[node] = 0;
return node;
}
void insert_ (int *ptr,int len) { // 这里插入的不是字符而是数字
int index,cur = 0;
for (int i = 0; i < len; ++i) {
index = ptr[i];
if(!child[cur][index]) {
child[cur][index] = newnode ();
}
cur = child[cur][index];
}
val[cur] = 1;
}
void getfail () {
queue<int> qu; int cur;
for (int i = 0; i < 266; ++i) {
if(child[0][i]) qu.push(child[0][i]);
}
while (!qu.empty()) {
cur = qu.front(); qu.pop();
for (int i = 0; i < 266; ++i) {
if(child[cur][i]) {
fail[child[cur][i]] = child[fail[cur]][i];
qu.push(child[cur][i]);
} else child[cur][i] = child[fail[cur]][i];
}
}
}
int query (int *str,int len) {
int cur = 0,index,p,ans = 0;
for (int i = 0; i < len; ++i) {
index = str[i];
cur = child[cur][index];
p = cur;
while (p && !vis[p]) {
if(val[p] > 0) {
ans++;
vis[p] = 1; // 走过就标记为1
}
p = fail[p];
}
}
return ans;
}
} ac;
char s[Maxn];
int pt[Maxn],num;
int bit_6[266]; // 存放对应base64字符的ascall,比如bit_6['A'] = 0;
char base64Index[66] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
void change (char *str, int *buf) {
memset(buf,0,sizeof(buf)); num = 0;
int len = strlen(str),index = 0,j = 0;
for (int i = 0; i < len; ++i) {
if(str[i] == '=') break;
index = index << 6| bit_6[str[i]];
j+=6;
if(j >= 8) { // 不足8个就继续补入字符的二进制数
buf[num++] = (index >> (j-8))& 0xff;
j-=8;
}
}
}
int main(void)
{
int n,m;
for (int i = 0; i < 64; ++i) bit_6[base64Index[i]] = i;
while (scanf("%d",&n) != EOF) {
ac.init();
for (int i = 0; i < n; ++i) {
scanf(" %s",s);
change(s,pt);
ac.insert_(pt,num);
}
ac.getfail();
scanf("%d",&m);
for (int i = 0; i < m; ++i) {
scanf(" %s",s);
change(s,pt);
memset(vis,0,sizeof(vis)); // 注意每换一个主字符串就要初始化一遍
printf("%d\n",ac.query(pt,num));
}
printf("\n");
}
return 0;
}