猫树总结

猫树

这个时候就要\(\texttt{orz immortalCO}\)了。

问题描述

现在你有一些无修改的信息,然后有多组询问,每一次对区间进行询问,信息满足结合律和可交换性。

解法

我们定义一种算法的复杂度为\(O(A)+O(B)+O(C)\),分别表示预处理复杂度,单次询问复杂度和空间复杂度。

可减信息

直接数组维护。\(O(n)+O(1)+O(n)\)

区间最值

倍增的\(RMQ\)解决即可。\(O(nlogn)+O(1)+O(nlogn)\)

只支持结合律和可交换性

一般采用线段树。\(O(n)+O(logn)+O(n)\)

一种新的解法:猫树

我们仔细思考一下,对于一个点\(o\)而言,如果他需要递归两个儿子等同于\(L\le l \le mid \le r \le R\)

因为信息满足结合律和可交换律,所以我们只需要对于两边快速合并即可。

然后对于每一层我们可以快速求出点\(i\)对于\(mid\)(\(i<mid\))以及\((i \ge mid)\)的答案。

此时唯一需要的就是快速定位点\(o\),如果按照一般的线段树建树显然不好定位,但是如果我们建一棵\(2^{k}\)的树,就很容易定位了。

具体来说,同时遍历两个点的意义就是这两个点的\(Lca\)对吧,相当于是你只要求出向上位移是多少即可。

我们不妨把两两个点看成1,每4个点看成2,那么相当于就是这一段它们的异或值的\(Log2\)对吧。

所以定位的深度为\(Log2[pos[l]]-Log2[[pos[l] xor pos[r]]\)

这时我们便找到了一个新的算法:猫树,\(O(nlogn)+O(1)+O(nlogn)\)

看上去很棒!

当然猫树其实只需要保证询问是\(O(1)\)的,只要预处理不超时都还行,所以比较适合那些询问比较多的题目。

GSS1

大致像线段树一样维护\(lmax,rmax,lsum,rsum\)然后合并即可。

附上曾经的题解

/*
  mail: mleautomaton@foxmail.com
  author: MLEAutoMaton
  This Code is made by MLEAutoMaton
*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<math.h>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#include<iostream>
using namespace std;
#define ll long long
#define re register
#define file(a) freopen(a".in","r",stdin);freopen(a".out","w",stdout)
inline int gi()
{
	int f=1,sum=0;char ch=getchar();
	while(ch>'9' || ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0' && ch<='9'){sum=(sum<<3)+(sum<<1)+ch-'0';ch=getchar();}
	return f*sum;
}
const int N=200010,Lg=20;
int a[N],n,pos[N],Log[N<<2];
int p[Lg][N],s[Lg][N];
void build(int o,int l,int r,int dep)
{
	if(l==r)
	{
		pos[l]=o;
		return;
	}
	int mid=(l+r)>>1;
	build(o<<1,l,mid,dep+1);build(o<<1|1,mid+1,r,dep+1);
	p[dep][mid]=s[dep][mid]=a[mid];int sum=a[mid],mx=max(0,a[mid]);
	for(int i=mid-1;i>=l;i--)
	{
		mx+=a[i];sum+=a[i];
		p[dep][i]=max(p[dep][i+1],mx);
		s[dep][i]=max(s[dep][i+1],sum);
		if(mx<0)mx=0;
	}
	p[dep][mid+1]=s[dep][mid+1]=a[mid+1];sum=a[mid+1],mx=max(0,a[mid+1]);
	for(int i=mid+2;i<=r;i++)
	{
		mx+=a[i];sum+=a[i];
		p[dep][i]=max(p[dep][i-1],mx);
		s[dep][i]=max(s[dep][i-1],sum);
		if(mx<0)mx=0;
	}
}
int query(int l,int r)
{
	if(l==r)return a[l];
	int dep=Log[pos[l]]-Log[pos[l]^pos[r]];
	return max(s[dep][l]+s[dep][r],max(p[dep][l],p[dep][r]));
}
int main()
{
#ifndef ONLINE_JUDGE
	freopen("in.in","r",stdin);
#endif
	n=gi();
	for(int i=1;i<=n;i++)a[i]=gi();
	int limit=1;
	while(limit<n)limit<<=1;
	build(1,1,limit,1);int T=gi();
	for(int i=2;i<=limit<<1;i++)Log[i]=Log[i>>1]+1;
	while(T--)
	{
		int l=gi(),r=gi();
		printf("%d\n",query(l,r));
	}
	return 0;
}

[Hnoi2016]序列

如果是原题的数据范围大可以莫队做法,但是现在我们限制询问有\(1e6\),即\(Q \le 10^6\)

即现在我们讨论的这题是这个

直接猫树维护很多东西即可。

/*
  mail: mleautomaton@foxmail.com
  author: MLEAutoMaton
  This Code is made by MLEAutoMaton
*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<math.h>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#include<iostream>
using namespace std;
#define ll long long
#define re register
#define file(a) freopen(a".in","r",stdin);freopen(a".out","w",stdout)
inline int gi()
{
	int f=1,sum=0;char ch=getchar();
	while(ch>'9' || ch<'0'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0' && ch<='9'){sum=(sum<<3)+(sum<<1)+ch-'0';ch=getchar();}
	return f*sum;
}
const int N=500010,Lg=22,Inf=1e9+10,Mod=1e9+7;
int n,a[N],sta[N],Log[N<<1];
int pos[N],b[N],id[Lg][N],Mid[Lg][N];ll p[Lg][N],s[Lg][N],ans[Lg][N];
int A, B, C, P;
long long lastAns;

inline int rnd() {
    return A = (A * B + (C ^ (int)(lastAns & 0x7fffffffLL)) % P) % P;
}
void build(int o,int l,int r,int dep)
{
	if(l==r)
	{
		pos[l]=o;return;
	}
	int mid=(l+r)>>1;build(o<<1,l,mid,dep+1);build(o<<1|1,mid+1,r,dep+1);
	int top,mn;ll delta,sum,pre;
	sta[top=0]=mid+1;delta=sum=pre=0;mn=Inf;
	for(int i=mid;i>=l;i--)
	{
		int v=a[i];Mid[dep][i]=mid;
		while(top && v<=a[sta[top]])
		{
			delta-=1ll*(sta[top-1]-sta[top])*a[sta[top]];
			top--;
		}
		delta+=1ll*v*(sta[top]-i);
		sta[++top]=i;mn=min(mn,v);b[i]=mn;
		sum+=delta;pre+=mn;
		s[dep][i]=sum;p[dep][i]=pre;
	}
	sta[top=0]=mid;delta=sum=pre=0;mn=Inf;
	for(int i=mid+1;i<=r;i++)
	{
		int v=a[i];Mid[dep][i]=mid;
		while(top && v<=a[sta[top]])
		{
			delta-=1ll*(sta[top]-sta[top-1])*a[sta[top]];
			top--;
		}
		delta+=1ll*v*(i-sta[top]);
		sta[++top]=i;mn=min(mn,v);
		sum+=delta;pre+=mn;b[i]=mn;
		s[dep][i]=sum;p[dep][i]=pre;
	}
	int pl=mid,pr=mid+1,in=0;sum=0;
	while(l<=pl || pr<=r)
	{
		if(pr>r || (l<=pl && b[pl]>b[pr]))
		{
			id[dep][pl]=++in;int len=in-(mid-pl+1);
			if(len)sum+=1ll*len*b[pl];
			ans[dep][pl--]=sum;
		}
		else
		{
			id[dep][pr]=++in;int len=in-(pr-mid);
			if(len)sum+=1ll*len*b[pr];
			ans[dep][pr++]=sum;
		}
	}
}
ll query(int l,int r)
{
	if(l==r)return a[l];
	int dep=Log[pos[l]]-Log[pos[l]^pos[r]],inl=id[dep][l],inr=id[dep][r],mid=Mid[dep][l];
	ll Ans=s[dep][l]+s[dep][r];
	if(inl<inr)Ans+=ans[dep][l]+1ll*(mid-l+1)*(p[dep][r]-((inl==mid-l+1)?0:p[dep][l+inl-1]));
	else Ans+=ans[dep][r]+1ll*(r-mid)*(p[dep][l]-((inr==r-mid)?0:p[dep][r-inr+1]));
	return Ans;
}
int main()
{
#ifndef ONLINE_JUDGE
	freopen("in.in","r",stdin);
#endif
	n=gi();int limit=1;
	while(limit<n)limit<<=1;
	for(int i=2;i<=limit<<1;i++)Log[i]=Log[i>>1]+1;
	int Q=gi();for(int i=1;i<=n;i++)a[i]=gi();
	build(1,1,limit,1);
	A=gi();B=gi();C=gi();P=gi();ll Ans=0;
	while(Q--)
	{
		int l = rnd() % n + 1, r = rnd() % n + 1;
		if (l > r) std::swap(l, r);
		lastAns=query(l,r);Ans=(Ans+lastAns)%Mod;
	}
	printf("%lld\n",(Ans+Mod)%Mod);
	return 0;
}
posted @ 2019-08-28 22:38  QwQGJH  阅读(293)  评论(0编辑  收藏  举报