题解: 在trie树上构造SAM 首先对于操作1 我们可以边加入边维护答案 通过增减dis[x]-dis[fa[i]]来得到 对于操作二 直接dfs建树即可 和原本构造trie树一样 对于操作三 用LCT维护 具有'价值'节点出现的次数 这个用LCT维护子树信息可以达到 然后就解决问题了
#include <algorithm> #include <iostream> #include <cstring> #include <cstdio> #include <vector> #include <stack> #include <queue> #include <cmath> #include <set> #include <map> #define mp make_pair #define pb push_back #define pii pair<int,int> #define link(x) for(edge *j=h[x];j;j=j->next) #define inc(i,l,r) for(int i=l;i<=r;i++) #define dec(i,r,l) for(int i=r;i>=l;i--) const int MAXN=3e5+10; const double eps=1e-8; #define ll long long using namespace std; struct edge{int t;char v;edge*next;}e[MAXN<<1],*h[MAXN],*o=e; void add(int x,int y,char ch){o->t=y;o->v=ch;o->next=h[x];h[x]=o++;} ll read(){ ll x=0,f=1;char ch=getchar(); while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();} while(isdigit(ch))x=x*10+ch-'0',ch=getchar(); return x*f; } char str[MAXN]; int ch[MAXN][2],pre[MAXN],key[MAXN],vis[MAXN];//size zong sz xu ll size[MAXN],sz[MAXN]; bool rt[MAXN],pis[MAXN]; int cur,cnt,n,m; ll ans; void newnode(int x){size[x]=key[x]=sz[x]=ch[x][0]=ch[x][1]=0;pre[x]=0;rt[x]=1;} void up(int x){size[x]=size[ch[x][0]]+size[ch[x][1]]+sz[x]+key[x];} void rotate(int x,int kind){ int y=pre[x]; pre[ch[x][kind]]=y;ch[y][!kind]=ch[x][kind]; if(rt[y])rt[x]=1,rt[y]=0; else ch[pre[y]][ch[pre[y]][1]==y]=x; pre[x]=pre[y];ch[x][kind]=y;pre[y]=x; up(y); } void splay(int x){ while(!rt[x]){ if(rt[pre[x]])rotate(x,ch[pre[x]][0]==x); else{ int y=pre[x];int kind=ch[pre[y]][0]==y; if(ch[y][kind]==x)rotate(x,!kind),rotate(x,kind); else rotate(y,kind),rotate(x,kind); } } up(x); } void access(int x){ int y=0; while(x){ splay(x); if(ch[x][1])pre[ch[x][1]]=x,rt[ch[x][1]]=1,sz[x]+=size[ch[x][1]]; if(y)rt[y]=0,sz[x]-=size[y]; ch[x][1]=y;up(x); y=x;x=pre[x]; } } void destory(int u,int v){access(u);splay(v);ch[v][1]=pre[u]=0;rt[u]=1;up(v);} void Link(int u,int v){ access(u);splay(u); access(v); splay(v); pre[u]=v;sz[v]+=size[u]; } ll querty(int u){ access(u);splay(u); return size[u]-size[ch[u][0]]; } int dis[MAXN],ch0[MAXN][26],fa[MAXN],f[MAXN]; int built(int x){ int last=cur;cur=++cnt;int p=last;dis[cur]=dis[p]+1;newnode(cur);size[cur]=key[cur]=1; for(;p&&!ch0[p][x];p=fa[p])ch0[p][x]=cur; if(!p)fa[cur]=1,Link(cur,1),ans+=dis[cur]-dis[fa[cur]]; else{ int q=ch0[p][x]; if(dis[q]==dis[p]+1)fa[cur]=q,Link(cur,q),ans+=dis[cur]-dis[q]; else{ int nt=++cnt;dis[nt]=dis[p]+1;newnode(nt); memcpy(ch0[nt],ch0[q],sizeof(ch0[q])); destory(q,fa[q]);ans-=dis[q]-dis[fa[q]]; fa[nt]=fa[q];Link(nt,fa[q]);ans+=dis[nt]-dis[fa[nt]]; fa[q]=fa[cur]=nt;Link(q,nt);Link(cur,nt);ans+=dis[q]-dis[nt];ans+=dis[cur]-dis[nt]; for(;ch0[p][x]==q;p=fa[p])ch0[p][x]=nt; } } return cur; } void dfs(int x,int pre){ f[x]=pre; link(x){ if(j->t!=pre&&!pis[j->t]){ pis[j->t]=1;cur=vis[x];vis[j->t]=built(j->v-'a'); dfs(j->t,x); } } } ll slove(){ scanf("%s",str);int len=strlen(str); int temp=1; for(int i=0;i<len;i++){ int t=str[i]-'a'; if(!ch0[temp][t])return 0; temp=ch0[temp][t]; } return querty(temp); } int main(){ read();vis[1]=1;newnode(1);cur=cnt=1;n=read();char ch;int u,v;ans=0; inc(i,1,n-1)u=read(),v=read(),scanf(" %c",&ch),add(u,v,ch),add(v,u,ch); dfs(1,0); m=read();int op,Rt,num; while(m--){ op=read(); if(op==1)printf("%lld ",ans); else if(op==2){ Rt=read();num=read(); inc(i,1,num-1)u=read(),v=read(),scanf(" %c",&ch),add(u,v,ch),add(v,u,ch); dfs(Rt,f[Rt]); } else printf("%lld ",slove()); } }
4545: DQS的trie
Time Limit: 10 Sec Memory Limit: 256 MBSubmit: 248 Solved: 90
[Submit][Status][Discuss]
Description
DQS的自家阳台上种着一棵颗粒饱满、颜色纯正的trie。
DQS的trie非常的奇特,它初始有n0个节点,n0-1条边,每条边上有一个字符。并且,它拥有极强的生长力:某个i时刻,某个节点就会新生长出一颗子树,它拥有si个节点且节点之间的边上有一个字符,并且新生长出来的子树也是一个树结构。然而因为是新长出来的,根据生活常识可知si必定不会大于i时刻之前的树的大小。
DQS定义trie的子串为从根节点(1号节点)往下走到所有节点所构成的字符串的所有的后缀。DQS身为一个单身doge,常常取出其中一个子串送给妹子,然而他并不希望送给妹子两个相同的子串,所以他非常关心当前trie的本质不同的子串数目。
DQS有时还会去商店购买子串,若他在商店看上某个子串,他希望得知这个子串是否在自家阳台的trie上已经出现,若出现则出现了多少次。如果出现了,他就可以直接回家取trie上的子串辣!
然而DQS身为一个蒟蒻,看着自家阳台的trie树一天天在长大,他被如此众多的节点弄得眼花缭乱,于是他找到了IOI2016Au的你。他会告诉你自家trie树的成长历程,他希望你能够对于每一次询问都做出正确回复。
Input
第一行输入一个整数id,代表测试点编号。
接下来一行输入一个整数n0,表示初始树的大小。
接下来n0-1行,每行两个整数u,v和一个字符c,表示u号节点和v号节点之间有一条边,边上的字母为c。
接下来输入m表示有m组操作。
对于每一组,第一行输入一个整数opt。
若opt=1,则是一组询问,询问当前trie的本质不同的子串数目是多少。
若opt=2,则后面跟两个整数rt,si,表示以点rt为根向下长出一个子树,大小为si。
接下来si-1行,每行两个整数u,v和一个字符c,表示u号节点和v号节点之间有一条边,边上的字母为c。若长出子树之前当前树的大小是n,则这si-1点的编号分别为n+1,n+2…n+si-1。
若opt=3,则是一组询问,后面输入一个字符串S,询问字符串S在当前trie中的出现次数。
Output
对于每个opt=1或3,输出一行表示答案。
Sample Input
1
4
1 2 a
1 3 b
2 4 b
6
1
2 2 4
2 5 b
2 6 c
5 7 b
1
3 ab
2 6 3
6 8 a
6 9 b
1
4
1 2 a
1 3 b
2 4 b
6
1
2 2 4
2 5 b
2 6 c
5 7 b
1
3 ab
2 6 3
6 8 a
6 9 b
1
Sample Output
3
7
2
11
7
2
11