前置技能
主席树 (模仿kuangbin,不用递归用while)
题目 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]求和就行了
坑点
- 用指针实现Trie会MLE,改为数组实现
- 主席树开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;
}