[BZOJ4545]DQS的Trie(广义SAM+离线+树状数组)
[BZOJ4545]DQS的Trie(广义SAM+离线+树状数组)
题面
一颗 Trie 树,q 次操作,操作有3种:
1.求这棵树上本质不同的子串数量
2.插入一个子树,保证总大小不超过 100000
3.询问一个字符串在 Trie 树上出现过多少次,保证所有询问串总长度不超过 100000
分析
不考虑插入,操作1就是问所有节点的\(len(x)-len(link(x))\)之和。操作3就是字符串在SAM上匹配到的节点,节点的right集合大小,即parent树子树大小(去掉复制出来的节点)之和。
SAM+LCT维护子树显然是可行的,但在考场上不一定能写出来。不妨把操作离线,把所有子树插入后的Trie树建出来,然后对它建广义后缀自动机。然后DFS整个parent树,记录DFS序,用来把子树和转化成区间和。容易发现虽然树的形态在变,但用最终树的DFS序来求和是等价的。于是可以用树状数组按照DFS序维护每个点的子树大小
接着遍历操作,发现操作2实际上就是最终自动机的一些节点由未出现变成出现的过程。首先按照DFS序单点加1,表示增加了一个出现的节点。然后沿着link往上跳,把节点由未出现标记成出现,跳到已经出现的节点位置。一边跳一边累加当前节点的\(len(x)-len(link(x))\)之和。由于每个点只会被标记一次,总复杂度是\(O(n)\)的。
对于询问,1操作直接输出累加的和。3操作在SAM上匹配,然后找到匹配到的节点,DFS序区间查询子树大小。
该离线做法不仅不容易写错,且常数比LCT小。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#define maxn 500000
#define maxc 26
using namespace std;
typedef long long ll;
template<typename T> void qread(T &x) {
x=0;
T sign=1;
char c=getchar();
while(c<'0'||c>'9') {
if(c=='-') sign=-1;
c=getchar();
}
while(c>='0'&&c<='9') {
x=x*10+c-'0';
c=getchar();
}
x=x*sign;
}
template<typename T> void qprint(T x) {
if(x<0) {
putchar('-');
qprint(-x);
} else if(x==0) {
putchar('0');
return;
} else {
if(x>=10) qprint(x/10);
putchar('0'+x%10);
}
}
int m;
struct qtype {
int opt;
string qs;//离线记录询问串
vector<int>subtr;//修改子树
} q[maxn+5];
struct fenwick_tree {
int sz;
int c[maxn+5];
inline int lowbit(int x) {
return x&(-x);
}
void add(int x,int val) {
for(int i=x; i<=sz; i+=lowbit(i)) c[i]+=val;
}
int sum(int x) {
int ans=0;
for(int i=x; i>0; i-=lowbit(i)) ans+=c[i];
return ans;
}
int query(int l,int r) {
return sum(r)-sum(l-1);
}
} B;
struct SAM {
#define len(x) (t[x].len)
#define link(x) (t[x].link)
struct node {
int len;
int link;
int ch[maxc];
} t[maxn+5];
vector<int>E[maxn+5];
const int root=1;
int ptr=1;
int extend(int last,int c) {
// if(t[last].ch[c]&&len(last)+1==len(t[last].ch[c])) return t[last].ch[c];
int p=last,cur=++ptr;
len(cur)=len(last)+1;
while(p&&!t[p].ch[c]) {
t[p].ch[c]=cur;
p=link(p);
}
// bool flag=0;
// int clo;
if(p==0) link(cur)=root;
else {
int q=t[p].ch[c];
if(len(p)+1==len(q)) link(cur)=q;
else {
// if(p==last) flag=1;
int clo=++ptr;
link(clo)=link(q);
len(clo)=len(p)+1;
for(int i=0; i<maxc; i++) t[clo].ch[i]=t[q].ch[i];
link(q)=link(cur)=clo;
while(p&&t[p].ch[c]==q) {
t[p].ch[c]=clo;
p=link(p);
}
}
}
return cur;
}
int tim;
int dfnl[maxn+5],dfnr[maxn+5];
void get_dfn(int x,int f) {
dfnl[x]=++tim;
for(int i=0; i<(int)E[x].size(); i++) {
int y=E[x][i];
if(y!=f) get_dfn(y,x);
}
dfnr[x]=tim;
}
bool vis[maxn+5];
ll strcnt=0;//本质不同子串个数
void update(int x) {
for(int y=x; y&&!vis[y]; y=link(y)) { //计算对子串个数的贡献
vis[y]=1;
strcnt+=len(y)-len(link(y));
}
B.add(dfnl[x],1);
}
ll query1() {
return strcnt;
}
int query3(string &s) {
int x=root;
for(int i=0; i<(int)s.length(); i++) {
int c=s[i]-'a';
if(!t[x].ch[c]) return 0;
x=t[x].ch[c];
}
return B.query(dfnl[x],dfnr[x]);
}
void build_fail() {
for(int i=2; i<=ptr; i++) E[link(i)].push_back(i);
get_dfn(1,0);
B.sz=tim;
}
} T1;
struct Trie {
vector<pair<int,int> >E[maxn+5];
void add_edge(int u,int v,int w) {
E[u].push_back(make_pair(v,w));
E[v].push_back(make_pair(u,w));
}
int pos[maxn+5];
void bfs(int id,int s) {//bfs建广义SAM
static bool vis[maxn+5];
queue<int>qu;
qu.push(s);
while(!qu.empty()) {
int x=qu.front();
qu.pop();
vis[x]=1;
if(x!=s) q[id].subtr.push_back(x);//根节点之前存在,不用更新
for(int i=0; i<(int)E[x].size(); i++) {
int y=E[x][i].first;
if(!vis[y]) {
pos[y]=T1.extend(pos[x],E[x][i].second);
qu.push(y);
}
}
}
}
} T2;
int main() {
int id,n0,u,v,rt,sz;
static char tmp[maxn+5];
qread(id);
qread(n0);
for(int i=1; i<n0; i++) {
qread(u);
qread(v);
scanf("%s",tmp);
T2.add_edge(u,v,tmp[0]-'a');
}
T2.pos[1]=1;
T2.bfs(0,1);
qread(m);
for(int i=1; i<=m; i++) {
qread(q[i].opt);
if(q[i].opt==2) {
qread(rt);
qread(sz);
for(int j=1; j<sz; j++) {
qread(u);
qread(v);
scanf("%s",tmp);
T2.add_edge(u,v,tmp[0]-'a');
}
T2.bfs(i,rt);
} else if(q[i].opt==3) {
scanf("%s",tmp);
q[i].qs=string(tmp);
}
}
T1.build_fail();
for(int j=0; j<(int)q[0].subtr.size(); j++) {//更新初始答案
int x=q[0].subtr[j];
T1.update(T2.pos[x]);
}
for(int i=1; i<=m; i++) {
if(q[i].opt==1) {
qprint(T1.query1());
putchar('\n');
} else if(q[i].opt==2) {
for(int j=0; j<(int)q[i].subtr.size(); j++) {
int x=q[i].subtr[j];
T1.update(T2.pos[x]);
}
}else{
qprint(T1.query3(q[i].qs));
putchar('\n');
}
}
}
版权声明:因为我是蒟蒻,所以请大佬和神犇们不要转载(有坑)的文章,并指出问题,谢谢