nssl 1489.大冰隙2或码队的跑团历险

D e s c r i p t i o n Description Description

给定一个长度为 n n n的序列,每个点有初始的权值 v i v_i vi和标记值 z l i zl_i zli,有 m m m次操作

  1. 单点修改某个节点的值
  2. 查询区间 [ l , r ] [l,r] [l,r]破裂后剩余数的 v i v_i vi的最大值

破裂的规则:在该区间内【后面位置判断你的是 z l zl zl】,如果存在一个位置 l l l是0,下一个位置 r = l + 1 r=l+1 r=l+1是1,则 l , r l,r l,r破裂,接着令 l = l − 1 , r = r + 1 l=l-1,r=r+1 l=l1,r=r+1继续判断,直到不合法为止

数据范围: n ≤ 1 0 5 n\leq 10^5 n105


S o l u t i o n Solution Solution

方法1

  • 树套树:容易发现破裂后的序列必然是一段前缀的1和一段后缀的0的区间,这些区间可以看做线段树上的区间,内部再开一棵线段树维护前缀1和后缀0的和即可,时间复杂度两个 l o g log log

方法2(接下来讲解的做法)

  • 0 0 0看做入栈, 1 1 1看做出栈,那么原 z l zl zl序列可以看做一个入栈出栈序列( d f n dfn dfn序),我们可以根据这个 d f n dfn dfn序建一棵树,那么原来序列上的点就变成了新树上的边,而一个入栈操作和一个出栈操作是恰好抵消的,所以查询 x ∼ y x\sim y xy未破裂的 v i v_i vi的最大值,实际上是查询路径上的最大值,用树链剖分+线段树维护即可,复杂度也是两个 l o g log log,但常数更小

细节很多,这里用样例来解释

建树

定义三个变量 n o w , r o o t , n now,root,n now,root,n,分别表示现在所处的节点,根节点,树的大小

  • 对于0操作,则新建一个节点( n + + n++ n++),作为 n o w now now的儿子,同时令 n o w = n now=n now=n【记得保存这条边对应是哪个点】
  • 对于1操作,则回退到它的父亲节点 n o w = f a [ n o w ] now=fa[now] now=fa[now],如果这个节点本身就是 r o o t root root,则令 r o o t = + + n root=++n root=++n,然后再跳

上述过程均需要建边

	n=1;root=1;now=1;
	for(register int i=1;i<=nn;i++)
	if(zl[i]==0) //指令是0
	{
		pos[i]=++n;//新建节点,第i跳边对应现在n这个点
		add(now,n);add(n,now);//建边
		val[n].first=v[i];//保存权值,first表示这个点的入边(从上面下来的边)
		fa[n]=now;now=n;//保存父亲节点,并往下跳跃
	}
	else//否则回跳
	{
		if(now==root)//已经是根了
		{
			pos[i]=now;//保存第i跳边对应now这个点
			val[now].second=v[i];
			root=++n;//新的根
			add(now,n);add(n,now);
			now=root;
		}
		else
		{
			pos[i]=now;
			val[now].second=v[i];
			now=fa[now];//回退
		}
	}

nssl 1489.大冰隙2或码队的跑团历险_第1张图片
这样我们初步建出了这样一张图,红色表示这条边原来的编号

但是树链剖分+线段树是只能求点值的,所以我们把每条边的值都存到深度较深的那个点上,用一个二元组存(代码中有体现),变成下面这张图(第一维表示从上面下来的,第二维维表示上去的,若没有则为0)
nssl 1489.大冰隙2或码队的跑团历险_第2张图片
对于二元组的每一维,分别开线段树维护即可,注意在交换 x , y x,y x,y时,需要改变方向

时间复杂度: O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)


C o d e Code Code

#include 
#include
#include
#include
#define N 100010
#define LL long long
using namespace std;int nn,n,m,root,fa[N],l[N],tot,x,y,dep[N],siz[N],son[N],id[N],cnt,top[N],opt,v[N],now,pos[N];
pair<int,int>nval[N],val[N];
bool zl[N];
inline LL read()
{
	char c;LL d=1,f=0;
	while(c=getchar(),!isdigit(c)) if(c=='-') d=-1;f=(f<<3)+(f<<1)+c-48;
	while(c=getchar(),isdigit(c)) f=(f<<3)+(f<<1)+c-48;
	return d*f;
}
struct node{int next,to;}e[N<<1];
inline void add(int u,int v){e[++tot]=(node){l[u],v};l[u]=tot;return;}
inline void dfs1(int x)//以下是树链剖分部分
{
	siz[x]=1;
	int maxn=0; 
	for(register int i=l[x];i;i=e[i].next)
	{
		int y=e[i].to;
		if(y==fa[x]) continue;
		dep[y]=dep[x]+1;fa[y]=x;
		dfs1(y);
		siz[x]+=siz[y];
		if(siz[y]>maxn)	maxn=siz[son[x]=y];
	}
	return;
}
inline void dfs2(int x,int topf)
{
	id[x]=++cnt;
	top[x]=topf;
	nval[cnt]=val[x];
	if(son[x]==0) return;
	dfs2(son[x],topf);
	for(register int i=l[x];i;i=e[i].next)
	{
		int y=e[i].to;
		if(y==fa[x]||y==son[x]) continue;
		dfs2(y,y);
	}
	return;
}
struct xds//线段树维护部分
{
	#define lson k<<1
	#define rson k<<1|1
	int maxn[N<<3];
	inline void pushup(int k)
	{
		maxn[k]=max(maxn[lson],maxn[rson]);
		return;
	}
	inline void build(bool flg,int k=1,int l=1,int r=cnt)
	{
		if(l==r)
		{
			if(flg==0) maxn[k]=nval[l].first;
			else maxn[k]=nval[l].second;//记得上行和下行分别维护
			return;
		}
		int mid=l+r>>1;
		build(flg,lson,l,mid);build(flg,rson,mid+1,r);
		pushup(k);
		return;
	}
	inline void Modify(bool flg,int x,int v,int k=1,int l=1,int r=cnt)
	{
		if(l==r) 
		{
			if(flg==0)nval[l].first=v;
			else nval[l].second=v;//分别赋值
			maxn[k]=v;return;
		}
		int mid=l+r>>1;
		if(x<=mid) Modify(flg,x,v,lson,l,mid);
		if(x>mid) Modify(flg,x,v,rson,mid+1,r);
		pushup(k);
		return;
	}
	inline int Ask(int ql,int qr,int k=1,int l=1,int r=cnt)
	{
		if(ql>qr) return 0;
		if(ql<=l&&r<=qr) return maxn[k];
		int res=0;int mid=l+r>>1;
		if(ql<=mid) res=max(res,Ask(ql,qr,lson,l,mid));
		if(qr>mid) res=max(res,Ask(ql,qr,rson,mid+1,r));
		pushup(k);
		return res;
	}
	#undef lson
	#undef rson	
}T1,T2;
inline int Query(bool flg,int x,int y)
{
	int res=0;
	while(top[x]!=top[y])
	{
		if(dep[top[x]]<dep[top[y]]) swap(x,y),flg^=1;//变向时要改变方向
		if(flg==0) res=max(T1.Ask(id[top[x]],id[x]),res);
		else res=max(T2.Ask(id[top[x]],id[x]),res);//上行和下行分别计算
		x=fa[top[x]];
	}
	if(dep[x]>dep[y]) swap(x,y);
	else flg^=1;
	if(id[x]==id[y]) return res;//同一个点无需再算
	if(flg==0) res=max(T1.Ask(id[son[x]],id[y]),res);//注意是son[x],因为它们的LCA是不做贡献的
	else res=max(T2.Ask(id[son[x]],id[y]),res);
	return res;
}
signed main()
{
	int size = 256 << 20; //250M
    char*p=(char*)malloc(size) + size;
    __asm__("movl %0, %%esp\n" :: "r"(p) );
	nn=read();m=read();
	for(register int i=1;i<=nn;i++) zl[i]=read();
	for(register int i=1;i<=nn;i++) v[i]=read();
	n=1;root=1;now=1;
	for(register int i=1;i<=nn;i++)
	if(zl[i]==0) //指令是0
	{
		pos[i]=++n;//新建节点,第i跳边对应现在n这个点
		add(now,n);add(n,now);//建边
		val[n].first=v[i];//保存权值,first表示这个点的入边(从上面下来的边)
		fa[n]=now;now=n;//保存父亲节点,并往下跳跃
	}
	else//否则回跳
	{
		if(now==root)//已经是根了
		{
			pos[i]=now;//保存第i跳边对应now这个点
			val[now].second=v[i];
			root=++n;//新的根
			add(now,n);add(n,now);
			now=root;
		}
		else
		{
			pos[i]=now;
			val[now].second=v[i];
			now=fa[now];//回退
		}
	}
	dfs1(root);
	dfs2(root,root);
	T1.build(0);T2.build(1);
	while(m--)
	{
		opt=read();
		if(opt==1)
		{
			x=read();y=read();
			v[x]=y;//顺便修改
			if(zl[x]==0) T1.Modify(0,id[pos[x]],y);//单点修改
			else T2.Modify(1,id[pos[x]],y);
		}
		if(opt==2)
		{
			x=read();y=read();
			if(x==y)
			{
				printf("%d\n",v[x]);//直接输出
				continue;
			}
			int px=pos[x],py=pos[y];//用树上的点
			if(px==py&&zl[x]!=zl[y])//两边相反
			{
				printf("0\n");
				continue;
			}
			if(zl[x]==0) px=fa[px];//这个点没有贡献,跳到它的父亲
			if(zl[y]==1) py=fa[py];//同理
			printf("%d\n",Query(1,px,py));//查询
		}
	}
}

你可能感兴趣的:(树套树,树链剖分,线段树,nssl,1489,大冰隙2,码队的跑团历险)