码迷,mamicode.com
首页 > 其他好文 > 详细

【BZOJ3879】SvT 后缀树+虚树

时间:2015-01-17 18:07:00      阅读:441      评论:0      收藏:0      [点我收藏+]

标签: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 后缀树+虚树

标签:bzoj3879   svt   后缀树   虚树   后缀自动机   

原文地址:http://blog.csdn.net/vmurder/article/details/42806431

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!