HDU 2846 Repository (Trie)

题意
      输入p个只含小写字母的字符串,然后再逐个输入q个只含小写字母的字符串str,查询p个字符串中有多少个字符串有str这个子串,输出其个数。

数据范围的一些限定
1 <= p <= 10000
1 <= q <= 100000
所有字符串长度都不超过20个字母

思路
      暴力的做法先输入p个字符串,然后每输入一个字符串str,就从p个字符串中的第一个开始到最后一个,一个一个检测str是否是这p个字符串的子串,判断str是否是别的字符串tmp的子串的方法大家应该都懂,就是枚举子串在tmp中的起始位置,然后逐位比较,直到比较完str或者找到不相同的位为止(突然想起KMP,但是这道题不是用KMP),显然这种解法超时到跪。
      看到这种一个字符串跟一堆字符串逐个进行比较的操作的时候,就会想起Trie,但是Trie基本上只能用于比较前缀(比较前缀的操作在之前的题解中已经有讲,这里就省略了),因此,如果要用到Trie的话,必须想办法解决“查询str是否是字符串tmp的子串”这个操作。想一想前缀和子串能有什么联系,上面的暴力解法中讲到了枚举字符串tmp的起点,那么如果从该起点出发的字符串suffix以str为前缀,则tmp中必然包含str这个子串,如果枚举完tmp的所有suffix后仍然没有找到一个以str为前缀的,则tmp必然不包含str这个子串。
      然后想到,插入了所有p个字符串以后,在Trie中怎么实现上面的枚举字符串tmp的起点,这个显然是没法儿实现的,因为Trie中每一个节点都有可能有很多的子结点,而我们查找Trie的时候是用一种映射关系来定位子结点的位置的,但是,我们根本不知道tmp具体是什么,所以没办法定位tmp的单词结点(即最后一个字母的结点)的位置。既然在Trie里面没办法实现这个功能,就想想别的。上面既然想到要枚举tmp的起点,然后看看str是否为从该起点出发的字符串suffix的前缀的话,那干脆在插入时就把枚举起点后的每一个suffix都插入Trie中不就好了么。
      题目要求是查询p个字符串中有多少个字符串有str这个子串,我们要从tmp中枚举出好多个子串(这些子串的来源都是tmp)出来一起插入Trie,如果这些相同来源的子串有不止一个以str为前缀,那么可能会出现重复计数的问题。假设我们枚举起点从左侧开始,并假设tmp枚举结点后有两个不同(长度不同,肯定是不同的)的子串suffix1和suffix2都以str为前缀,suffix1长度大于suffix2,则插入suffix2的操作肯定在插入suffix1的操作之后,在插入suffix2时,对于每一个在suffix1已经计数过的字母结点来说,就不应该再计一次数,因此,我们需要用一个变量记录当前字母结点是否已经对tmp计过数,若是,则不计数,若不是,则要计数,为了标记每个字母结点,则要开一个很大的数组,这样感觉有点浪费空间,那么能不能从现有的数组中拿一个来提供标记用途呢,答案是可以的。这题是查询前缀,则对单词结点的标记显得不重要,因此我们可以利用val数组来进行标记,记录当前字母结点来源于哪一个字符串tmp,用于区分。
      至于计数,只要开多一个数组记录每一个字母结点的插入次数就行了,最后查询的时候直接输出所查询字符串的最后一个字符的插入次数。
      写了这么多,不知道有没有讲清楚,更具体一些的细节请自己看下面附上的代码吧。

      以下是代码,各种多余头文件宏定义什么的请无视,在vimrc里写好了,懒得删掉了,可以视为我在装13。如果在其中发现有错误,请指出,发表此文部分原因是希望有人能够指出我可能存在的错误。如果有人有更好的解法,请不吝赐教。
/*
 * Author:  Fiend
 * Created Time:  2013/4/10 11:24:26
 * File Name: test.cpp
 */
#include <iostream>
#include <cstdio>
#include <cstddef>
#include <cmath>
#include <algorithm>
#include <string>
#include <cstring>
#include <vector>
#include <bitset>
#include <stack>
#include <queue>
#include <set>
#include <map>
#include <cctype>

#define ST size_type
#define PB push_back
#define LL long long
#define NODE_SIZE 1000000
#define SIGMA_SIZE 26

using std::cin;
using std::cout;
using std::endl;
using std::string;
using std::bitset;
using std::vector;
using std::pair;
using std::swap;
using std::sort;
using std::max;
using std::min;

const int inf = 0x3fffffff;

typedef pair<int, int> pii;
typedef vector<int> vi;
typedef vector<int>::iterator vit;

class Trie {
    private:
        int child[NODE_SIZE][SIGMA_SIZE];
        int val[NODE_SIZE];
        int count[NODE_SIZE];
        int cnt;    //结点总数
    public:
        //初始时只有一个根结点
        Trie () {
            cnt = 1;
            count[0] = 0;
            memset (child[0], 0, sizeof (child[0]));
        }

        //返回字符ch的编号
        int getIndex (char ch) {
            return ch -'a';
        }

        /*
         * 查询字符串target是否在trie中,是则返回其附加的信息,否则返回0
         * target是string类型
         * 对象做实参要加引用,不然每次一调用函数就要复制一个对象,很影响效率
         */
        int query (string &target) {
            int parent = 0, index;
            for (string::size_type i = 0; i != target.size(); ++i) {
                index = getIndex (target[i]);
                if (child[parent][index] == 0)
                    return 0;
                parent = child[parent][index];
            }
            return count[parent];
        }

        /*
         * 插入的子串的来源字符串src,附加信息为v,v代表该字母结点来源于哪一个字符串
         * src是string类型,插入前不用查询是否存在
         * 对象做实参要加引用,不然每次一调用函数就要复制一个对象,很影响效率
         */
        void insert (string &src, string::ST &j, int v) {  
            int parent = 0, index;  
            for (string::size_type i = j; i != src.size(); ++i) {  
                index = getIndex (src[i]);  
                if (child[parent][index] == 0) {    //结点不存在  
                    child[parent][index] = cnt;     //新建结点  
                    memset (child[cnt], 0, sizeof(child[cnt]));  
                    val[cnt] = 0;	//新结点,没有对src计过数
                    count[cnt] = 0;	//计数初始化为0,不初始化为1是因为在下面的if里还要加
                    ++cnt;  
                }
                parent = child[parent][index];  //往下走  
                if (val[parent] != v) {//之前没有对src计过数
                    ++count[parent];	//计数
                    val[parent] = v;	//标记已经对src计过数了
                }
            }  
        }  
};  

Trie t;

int main () {
    int p, q;
    string str;
    cin >> p;
    for (int i = 1; i <= p; ++i) {
        cin >> str;
        for (string::ST j = 0; j != str.size(); ++j)
            t.insert (str, j, i);
    }
    cin >> q;
    scanf("%d", &q);
    for (int i = 1; i <= q; ++i) {
        cin >> str;
        cout << t.query (str) << endl;
    }
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值