首页 > 代码库 > 【BZOJ3879】SvT 后缀树+虚树
【BZOJ3879】SvT 后缀树+虚树
转载请注明出处谢谢:http://blog.csdn.net/vmurder/article/details/42806431
SVT什么意思?
suffix virtual tree。
没有错!后缀虚树
好了,下面发一段以前的文字。
话说其实后缀数组分治能写,当时想shei了。
Vn:
啊,水题。
一看到“后缀”和这数据范围,肯定后缀数组、后缀自动机、后缀树走起!
然后我们可以轻松构造出来一个后缀树,然后每次询问树形DP随便乱搞就能过了。但是这个时候显然会TLE,所以我们可以尝试利用【LCA单调性】来【构建虚树】。
好了,解决了。
这里我可以再提供一种思路:
首先对于每次询问我们有一种暴力的算法(50分),就是假定有p个询问到的元素,那么O(p*p)枚举,然后最坏O(n)check,这样每次询问的时间复杂度是n^3。
而我们还可以对它进行优化:写一份后缀数组,然后预处理出RMQ,这样暴力枚举O(p*p),而check却是O(1)的,这样就可以写出优秀的每次询问O(p*p)代码了!
当然,提到后缀数组,我们会想到BZOJ的《差异》这道题,那我们每次询问也可以参照《差异》的做法,O(n)出解!这样的时间复杂度是O(n*m),依然无法过掉本题,但是已经很优秀了!
看到这里,我们发现,两种做法的时间复杂度,一个用p,一个用n,是不是可以混搭一下呢?
启发式暴力!!!
当p<=sqrt(n)的时候,我们可以用暴力1,否则我们用暴力2。这样随便YY一下,最坏的情况应该是所有询问的元素个数都是sqrt(n),那么我们就需要300W/sqrt(n)次询问,每次询问O(n),总的时间复杂度是3000*100W,也就是30Y,如果卡卡常数,再加上BZOJ计算的是总时限,再加上O(2),再加上肯定有人正解被卡常然后时限被放宽到30s,,说不定还真有人可以用这种方法过掉此题。
代码:
(除了第一个点我写的是有大于d的字母,其它都是a~d,针对这点可以改改内存哦~)
/* 时间复杂度: { SAM : O(n) 深搜: O(n) RMQ : O(nlogn) 虚树部分: 均摊O(∑) , 总之是200W } 空间复杂度: { 反正是大常数O(n) +个O(nlogn) } 代码复杂度 { 挺清晰的,没有恶心细节。 但是属于代码题。 省选 } */ #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #define N 1001000 #define LOGN 23 #define Qwq 26 using namespace std; struct KSD { int v,next; }e[N]; // e:原树 int head[N],cnt; inline void add(int u,int v) { cnt++; e[cnt].v=v; e[cnt].next=head[u]; head[u]=cnt; } int pa[N<<1],son[N<<1][Qwq],dep[N<<1],res[N]; bool mian[N]; int tot=1,last=1; inline int newnode(int _dep){dep[++tot]=_dep;return tot;} inline void SAM(int alp) { int p=newnode(dep[last]+1); int u=last; while(u&&!son[u][alp])son[u][alp]=p,u=pa[u]; if(!u)pa[p]=1; else { int v=son[u][alp]; if(dep[v]==dep[u]+1)pa[p]=v; else { int nv=newnode(dep[u]+1); pa[nv]=pa[v],pa[v]=pa[p]=nv; memcpy(son[nv],son[v],sizeof son[nv]); while(u&&son[u][alp]==v)son[u][alp]=nv,u=pa[u]; } } mian[p]=1; res[dep[p]]=last=p; } int fa[N<<1][LOGN],deep[N<<1],pos[N<<1]; // fa ST表父亲、deep树中深度、pos深搜序 void dfs(int x) { int i,v; pos[x]=++cnt; for(i=head[x];i;i=e[i].next) { v=e[i].v; fa[v][0]=x; deep[v]=deep[x]+1; dfs(v); } } void array(int n,int logn=LOGN-1) { int i,j; fa[1][0]=1; for(j=1;j<=logn;j++) for(i=1;i<=n;i++) fa[i][j]=fa[fa[i][j-1]][j-1]; } inline int getlca(int x,int y,int logn=LOGN-1) { int i; if(deep[x]<deep[y])swap(x,y); for(i=logn;i>=0;i--) if(deep[fa[x][i]]>=deep[y]) x=fa[x][i]; if(x==y)return x; for(i=logn;i>=0;i--) if(fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i]; return fa[x][0]; } struct Graph { struct AKL { int v,next,len; }e[N]; int head[N],cnt; int vis[N],yyc[N],T; void add(int u,int v) { e[++cnt].v=v; if(vis[u]!=T)vis[u]=T,e[cnt].next=0; else e[cnt].next=head[u]; head[u]=cnt; } void set(int x){yyc[x]=T;} long long ans; int dp(int x,int p) { int i,v,size=(yyc[x]==T?mian[x]:0); if(vis[x]!=T)return size; for(i=head[x];i;i=e[i].next) size+=dp(v=e[i].v,x); ans+=(long long)size*(size-1)*(dep[x]-dep[p])>>1; return size; } void DP() { ans=0; for(int i=head[1];i;i=e[i].next)dp(e[i].v,1); cout<<ans<<endl; } }G; int n,m; char str[N]; struct Lux { int x,pos; Lux(int _x=0,int _pos=0):x(_x),pos(_pos){} bool operator < (const Lux &a)const{return pos<a.pos;} }lux[N]; int stk[N],top; int main() { freopen("vn.in","r",stdin); freopen("vn.out","w",stdout); int i,j,k; scanf("%d%d",&n,&m); scanf("%s",str); int len=strlen(str); for(i=len-1;i>=0;i--)SAM(str[i]-'a'); for(i=2;i<=tot;i++)add(pa[i],i); cnt=0,deep[1]=1,dfs(1),array(tot); while(m--) { G.T++,G.cnt=0; for(scanf("%d",&n),i=1;i<=n;i++) { scanf("%d",&k); k=res[len-k+1]; lux[i]=Lux(k,pos[k]),G.set(k); } sort(lux+1,lux+n+1); stk[top=1]=1; for(i=1;i<=n;i++) { if(lux[i].x==lux[i-1].x)continue; int lca=getlca(stk[top],lux[i].x); while(deep[lca]<deep[stk[top]]) { if(deep[stk[top-1]]<=deep[lca]) { int last=stk[top--]; if(stk[top]!=lca)stk[++top]=lca; G.add(lca,last); break; } G.add(stk[top-1],stk[top]),top--; } if(stk[top]!=lux[i].x)stk[++top]=lux[i].x; } while(top>1)G.add(stk[top-1],stk[top]),top--; G.DP(); } fclose(stdin); fclose(stdout); return 0; }
【BZOJ3879】SvT 后缀树+虚树