【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;
}

时间: 2024-10-26 11:13:03

【BZOJ3879】SvT 后缀树+虚树的相关文章

BZOJ 3879 SvT 后缀树+虚树

题目大意:给出一个字符串,给出一些询问,每次问几个后缀两两之间的LCP之和. 思路:保证Σask数量级在O(n)上,可以考虑一下虚树了.建立虚树之后,这题就和差异那个题一样了.但是必须要打时间戳啊,要不死的很惨的啊.. CODE: #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #define MAX 1000010 using namespace s

51Nod1868 彩色树 虚树

原文链接https://www.cnblogs.com/zhouzhendong/p/51Nod1868.html 题目传送门 - 51Nod1868 题意 给定一颗 $n$个点的树,每个点一个 $[1,n]$ 的颜色.设 $g(x,y)$ 表示 $x$ 到 $y$ 的树上路径上有几种颜色. 对于一个长度为 $n$ 的排列 $P[1\cdots n]$ ,定义 $f(P)=\sum_{i=1}^{n-1}g(P_i,P_{i+1})$ . 现在求对于 $n!$ 个排列,他们的 $f(P)$ 之和

【2019北京集训测试赛(十三)】函树 虚树

题目大意:给你一颗$n$个节点的树,定义$d(x,y)=$点$x$到点$y$最短路上经过的边数. 求$\sum\limits_{i=1}^{n} \sum\limits_{j=1}^{n} \varphi(i\times j)\times d(i,j)$ 答案对998244353$取模. 我们对这个式子做一些细微的处理,设最终的答案为$ans$: $ans=\sum\limits_{i=1}^{n} \sum\limits_{j=1}^{n} \varphi(i\times j)\times d

CF1073G Yet Another LCP Problem 后缀自动机 + 虚树 + 树形DP

Code: #include <bits/stdc++.h> #define setIO(s) freopen(s".in","r",stdin) #define maxn 400004 #define ll long long using namespace std; int edges,n,Q; int hd[maxn],to[maxn],nex[maxn],tr[maxn]; char str[maxn]; void addedge(int u,i

虚树初探

虚树其实没什么的.. 只是因为点太多了不能全开于是只开那些需要用到的点. 一棵虚树包括要求点以及它们的lca.. 虚树的构建...(其实感觉如果会虚树的构建的话接下来就是树dp啦没什么的... 首先我们应该对整棵树dfs,求出它的dfs序列.然后对于给的点,按dfs排序.. 因为我们是按dfs序排列的,所以虚树一定是由一条条链构成的.. 扫一遍给的点,如果这个点在当前的这条链上,那加在栈顶就可以了. 如果不是的话,那就不断地退栈使的原来的那条链上面的边全部被加到边集中.. rep(i,1,n){

bzoj 2286 [Sdoi2011]消耗战(虚树+树上DP)

2286: [Sdoi2011]消耗战 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 1276  Solved: 445[Submit][Status][Discuss] Description 在一场战争中,战场由n个岛屿和n-1个桥梁组成,保证每两个岛屿间有且仅有一条路径可达.现在,我军已经侦查到敌军的总部在编号为1的岛屿,而且他们已经没有足够多的能源维系战斗,我军胜利在望.已知在其他k个岛屿上有丰富能源,为了防止敌军获取能源,我军的任务是炸

bzoj 3572 [Hnoi2014]世界树(虚树+DP)

3572: [Hnoi2014]世界树 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 645  Solved: 362[Submit][Status][Discuss] Description 世界树是一棵无比巨大的树,它伸出的枝干构成了整个世界.在这里,生存着各种各样的种族和生灵,他们共同信奉着绝对公正公平的女神艾莉森,在他们的信条里,公平是使世界树能够生生不息.持续运转的根本基石.     世界树的形态可以用一个数学模型来描述:世界树中有n个

BZOJ 3572: [Hnoi2014]世界树 [虚树 DP 倍增]

传送门 题意: 一棵树,多次询问,给出$m$个点,求有几个点到给定点最近 写了一晚上... 当然要建虚树了,但是怎么$DP$啊 大爷题解传送门 我们先求出到虚树上某个点最近的关键点 然后枚举所有的边$(f,x)$,讨论一下边上的点的子树应该靠谁更近 倍增求出分界点 注意有些没出现在虚树上的子树 注意讨论的时候只讨论链上的不包括端点,否则$f$的子树会被贡献多次 学到的一些$trick:$ 1.$pair$的妙用 2.不需要建出虚树只要求虚树的$dfs$序(拓扑序)和$fa$就可以$DP$了 注意

[SDOI2011][BZOJ2286] 消耗战|虚树|树型dp|树上倍增LCA

2286: [Sdoi2011]消耗战 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 1040  Solved: 363[Submit][Status][Discuss] Description 在一场战争中,战场由n个岛屿和n-1个桥梁组成,保证每两个岛屿间有且仅有一条路径可达.现在,我军已经侦查到敌军的总部在编号为1的岛屿,而且他们已经没有足够多的能源维系战斗,我军胜利在望.已知在其他k个岛屿上有丰富能源,为了防止敌军获取能源,我军的任务是炸