HDU 5790 Prefix(字典树、主席树)

前置技能

  1. 主席树 (模仿kuangbin,不用递归用while)

  2. 题目 spoj3290 传送门君

    给一串数列,有q个(1e5的数量级)询问,求i到j间的不同数字的个数

题意

N个串,Q个询问。询问第[L,R]的串里面有多少个不同的前缀

1<=N<=100000 ,Q<=1e5,串的总长度<=1e5

思路

主席树 + Trie

每次插入一个串s[i],相当于新建一棵线段树T[i]。(主席树记录历史状态)。在spoj3290中,T[i]中记录的是[l,r]区间内(1<=l<=r<=i),a[l...r]中不同数字的个数。而这题中,T[i]维护的是,对于字符串数组s[maxn],s[l]...s[r]中有多少不同的前缀。怎样维护的呢?我实现的主席树中,记c[o]为线段树的端点,o是一个区间的编号。这个c[o]维护的是什么呢?若o代表的区间是[j,j],即第j个串(第T[i]个线段树中)。记这些o为o1,o2,....(第1个串,第2个串...)记录的就是这个串s[j]中,最后一次出现的前缀个数。(至于为什么要记录最后一次出现,参考spoj3290的思路)
举个栗子(每次插入字符串到Trie中)

这里写图片描述

第一棵线段树中,Tree[1]的c[o1]+=3
第二棵线段树中, Tree[2]的c[o2]+=2
第三棵线段树问题来了,”abd”的三个前缀都是最后一次出现,所以Tree[3]的c[o3]+=3,但”a”,”ab”在之前的第一棵线段树中已经出现并记录过了,所以要把Tree[3]的c[o1]-=2;
第四棵树,Tree[4]的c[o4]+=4,那么第3棵树中的”ab”与“a”都不是最后一次出现了,Tree[4]的c[o3] -=2。第1棵树中的”abc”也不再是最后一次出现了。所以Tree[4]的c[o1]-=1;第1棵树里也有”ab”,要不要c[o1]再-=2呢?不需要。因为之前插入第3个串的时候,已经减去过了,不必重复再减。
所以说插入一个串i的时候,必然有Tree[i]的c[oi]+=strlen(s[i])
但应该减去Tree[i]的哪些c[o]呢,减去多少呢
所以用一个v[]记录Trie中的每个字符最后一次出现在哪个字符串里。见图。则对于插入的新串中的每个字符ch,那么就让Tree[i]的c[o v[ch] ]–;
例如第4棵树,插入“abce”,插入Trie的时候,第一个a的v为3,那么Tree[4]的c[o3]– 然后v[这个’a’]更新为4。v[‘b’]为3,Tree[4]的c[o3]– 然后v[这个’b’]更新为4。v[‘c’]为1,Tree[4]的c[o1]–; 然后v[这个’c’]更新为4。新建结点e, v[‘e’]就赋值为4
这样查询[L,R]的时候,就对于Tree[R]的c[L..R]求和就行了

坑点

  1. 用指针实现Trie会MLE,改为数组实现
  2. 主席树开15 * maxn还是会re ,开30才过(按理说应该开40吧)

代码

#include <vector>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include <iostream>
#include <stack>
#include <cmath>
#include <map>
#include <set>
#include <queue>
#include <deque>
using namespace std;
typedef long long ll;

#define MAX 26

const int maxn = 1e5+10;
const int maxm = 30 * maxn;
int N,Q;


//-----改掉Trie-----/
int nextt[maxn][MAX];
int v[maxn];
int totTrie;

int tot;
int a[maxn];
int T[maxn], lson[maxm], rson[maxm], c[maxm];

int build(int l,int r){
    int root = tot ++;
    c[root] = 0;
    if(l!=r){
        int mid = (l+r) >> 1;
        lson[root] = build(l,mid);
        rson[root] = build(mid+1 ,r);
    }
    return root;
}

int Insert(int root,int pos,int val){
    int newroot = tot++, tmp = newroot;
    int l = 1, r = N;
    c[newroot] = c[root] + val;
    while( l < r){
        int mid = (l + r) >> 1;
        if(pos <=mid){
            lson[newroot] = tot++;rson[newroot] = rson[root];
            newroot = lson[newroot]; root = lson[root];
            r = mid;
        }else {
            rson[newroot] = tot++; lson[newroot] = lson[root];
            newroot = rson[newroot]; root = rson[root];
            l = mid + 1;   
        }
        c[newroot] = c[root] +val;
    }
    return tmp;
}

int Query(int root, int p, int l, int r){
    if(l == p) return c[root];
    int mid = (l+r) >> 1;
    if( p <= mid) return Query(lson[root], p, l, mid) + c[rson[root]];
    return Query(rson[root], p, mid+1, r);
}


int now;//现在插入的是第几棵树

void InsertTrie(){
    char ch;
    int len = 0;
    int p = 0, q;
    while((ch=getchar()) != '\n'){
        len ++;
        int id = ch - 'a';
        if(nextt[p][id] == -1){
            q = ++ totTrie;
            v[q] = now;
            memset(nextt[q],-1,sizeof(nextt[q]));
            nextt[p][id] = q;
            p = q;   
        }    else {
            p = nextt[p][id];
            if(v[p] != 0 ) T[now] = Insert(T[now], v[p], -1);
            v[p] = now;
        }
    }
    T[now] = Insert(T[now], now, len);

}

int main() {
    //freopen("data.in","r",stdin);
    while( ~scanf("%d",&N)){
        getchar();
        memset(v,0,sizeof(v));
        memset(nextt[0], -1 ,sizeof(nextt[0]));
        now = totTrie = tot = 0;
        T[0] = build(1, N);
        for(int i = 1; i <= N; i++){
            ++ now;
            T[i] = T[i-1];
            InsertTrie();
        }

        scanf("%d",&Q);
        int x,y,z;
        z=0;
        while(Q--){
            scanf("%d%d",&x,&y);
            int L,R;
            L=min( (z+x)%N  , (z+y)%N ) +1;
            R=max( (z+x)%N, (z+y)%N) +1;
            z=Query(T[R], L, 1, N);
            printf("%d\n",z);
        }

    }
    return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值