BZOJ 3720 [洛谷P2137] : Gty的妹子树

11 篇文章 0 订阅
5 篇文章 0 订阅

Description

我曾在弦歌之中听过你,

檀板声碎,半出折子戏。

舞榭歌台被风吹去,

岁月深处尚有余音一缕……

Gty神(xian)犇(chong)从来不缺妹子……

他来到了一棵妹子树下,发现每个妹子有一个美丽度……

由于Gty很哲♂学,他只对美丽度大于某个值的妹子感兴趣。

他想知道某个子树中美丽度大于k的妹子个数。

某个妹子的美丽度可能发生变化……

树上可能会出现一只新的妹子……

维护一棵初始有n个节点的有根树(根节点为1),树上节点编号为1-n,每个点有一个权值wi。

支持以下操作:

0 u x 询问以u为根的子树中,严格大于x的值的个数。(u^=lastans,x^=lastans)

1 u x 把u节点的权值改成x。(u^=lastans,x^=lastans)

2 u x 添加一个编号为"当前树中节点数+1"的节点,其父节点为u,其权值为x。(u^=lastans,x^=lastans)

最开始时lastans=0。

Input

输入第一行包括一个正整数n(1<=n<=30000),代表树上的初始节点数。

接下来n-1行,每行2个整数u,v,为树上的一条无向边。

任何时刻,树上的任何权值大于等于0,且两两不同。

接下来1行,包括n个整数wi,表示初始时每个节点的权值。

接下来1行,包括1个整数m(1<=m<=30000),表示操作总数。

接下来m行,每行包括三个整数 op,u,v:

op,u,v的含义见题目描述。

保证题目涉及的所有数在int内。

Output

对每个op=0,输出一行,包括一个整数,意义见题目描述。

Sample Input

2

1 2

10 20

1

0 1 5

Sample Output

2

Solution

  • 大家多用的是树分块的方法,这里我用的归并树+定期重构。

  • 具体怎样呢?关键是我们考虑每个修改对之后询问的影响。

  • 如果没有修改(静态询问),我们对dfs序建归并树,直接区间查询即可。

  • (归并树就是一种线段树,区间内存的是这个区间权值排序后的序列,查询时在上面二分)

  • 有了修改,我们就要判断修改对询问有影响,其中修改点要在询问点的子树内。

  • 如何判断是否在子树内:倍增跳。加点时处理出其 2 i 2^i 2i 级父亲。

  • 于是我们得到这样一个算法:我们在归并树中查询后,针对若干修改操作暴力判断影响。

  • 那我们就可以定期重构啦!

  • 如果我们在查询之前的修改不超过 m \sqrt m m 次时,就在归并树上查询后暴力扫描修改计算贡献;

  • 如果修改超过了 m \sqrt m m 次时,我们只要根据修改重建一下归并树就可以清除掉这些修改,

  • 可以发现归并树的重建不会超过 m \sqrt m m 次。

  • 那么我们来分析一下复杂度:(假设 n , m n,m n,m 同阶)

  • 每次扫描修改算贡献,修改最多 n \sqrt n n 个,每次倍增判是否在子树要 O ( l o g   n ) O(log\ n) O(log n) ,复杂度为 O ( n n   l o g   n ) O(n\sqrt n\ log\ n) O(nn  log n)

  • 每次重建归并树要 O ( n   l o g   n ) O(n\ log\ n) O(n log n) ,最多重建 O ( l o g   n ) O(log\ n) O(log n) 次,故复杂度同是 O ( n n   l o g   n ) O(n\sqrt n\ log\ n) O(nn  log n)

  • 于是这题就解决了,总时间复杂度 O ( n n   l o g   n ) O(n\sqrt n\ log\ n) O(nn  log n)

  • 有一些细节要注意:

  • 由于重建归并树常数比较大,我们可以多几次修改再重建一次,比如说 5 ∗ n 5*\sqrt n 5n

  • 还有就是打线段树询问时: f i n d ( 1 , 1 , n ) find(1,1,n) find(1,1,n) ,由于加点时 n n n 会增加,但带进询问时仍然要是之前的 n n n ,不然就不对了,重构时再把 f i n d ( 1 , 1 , n ) find(1,1,n) find(1,1,n) n n n 改成新的。

Code

#include<cstdio>
#include<algorithm>
#include<cmath>
#include<vector>
#include<cctype>
using namespace std;
const int N=60005;
struct data
{
	int op,x,y,z;
}q[N>>1];
int n,tot,num,cnt,qx,qy,qz,last,lim;
int first[N],nex[N<<1],en[N<<1];
int w[N],dfn[N],size[N],id[N],dep[N],fa[N][16];
vector<int>ss[N<<2];
inline int read()
{
	int X=0,w=0; char ch=0;
	while(!isdigit(ch)) w|=ch=='-',ch=getchar();
	while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
	return w?-X:X;
}
void write(int x)
{
	if(x>9) write(x/10);
	putchar(x%10+'0');
}
inline void insert(int x,int y)
{
	nex[++tot]=first[x];
	first[x]=tot;
	en[tot]=y;
}
void dfs(int x)
{
	dfn[x]=++cnt;
	id[cnt]=x;
	size[x]=1;
	dep[x]=dep[fa[x][0]]+1;
	for(int i=first[x];i;i=nex[i])
		if(en[i]^fa[x][0])
		{
			fa[en[i]][0]=x;
			dfs(en[i]);
			size[x]+=size[en[i]];
		}
}
void make(int v,int l,int r)
{
	ss[v].clear();
	if(l==r)
	{
		ss[v].push_back(w[id[l]]);
		return;
	}
	int mid=l+r>>1,ls=v<<1,rs=ls|1;
	make(ls,l,mid);
	make(rs,mid+1,r);
	int i=0,ni=ss[ls].size()-1;
	int j=0,nj=ss[rs].size()-1;
	while(i<=ni && j<=nj) ss[v].push_back(ss[ls][i]<ss[rs][j]?ss[ls][i++]:ss[rs][j++]);
	while(i<=ni) ss[v].push_back(ss[ls][i++]);
	while(j<=nj) ss[v].push_back(ss[rs][j++]);
}
int find(int v,int l,int r)
{
	if(qx<=l && r<=qy) return ss[v].size()-(upper_bound(ss[v].begin(),ss[v].end()--,qz)-ss[v].begin());
	int mid=l+r>>1,s=0;
	if(qx<=mid) s=find(v<<1,l,mid);
	if(qy>mid) s+=find(v<<1|1,mid+1,r);
	return s;
}
void dfs1(int x)
{
	size[x]=1;
	dfn[x]=++cnt;
	id[cnt]=x;
	for(int i=first[x];i;i=nex[i])
		if(en[i]^fa[x][0])
		{
			dfs(en[i]);
			size[x]+=size[en[i]];
		}
}
inline void rebuild()
{
	cnt=num=0;
	dfs1(1);
	make(1,1,n);
	cnt=n;
}
inline bool belong(int x,int y)
{
	if(dep[x]<dep[y]) return false;
	for(int i=log2(dep[x]);i>=0;i--)
		if(dep[fa[x][i]]>=dep[y]) x=fa[x][i];
	return x==y;
}
int main()
{
	n=read();
	for(int i=1;i<n;i++)
	{
		int x=read(),y=read();
		insert(x,y);
		insert(y,x);
	}
	for(int i=1;i<=n;i++) w[i]=read();
	dfs(1);
	cnt=n;
	for(int j=1;j<16;j++)
		for(int i=1;i<=n;i++)
			fa[i][j]=fa[fa[i][j-1]][j-1];
	make(1,1,n);
	int m=read();
	lim=ceil(sqrt(m)*5);
	while(m--)
	{
		int op=read(),x=read()^last,y=read()^last;
		if(op==0)
		{
			if(x<=cnt)
			{
				qx=dfn[x],qy=dfn[x]+size[x]-1,qz=y;
				last=find(1,1,cnt);
			}else last=0;
			for(int i=1;i<=num;i++)
				if(q[i].op==1)
				{
					if((q[i].y<y)^(q[i].z<y) && belong(q[i].x,x)) last+=q[i].z>y?1:-1;
				}else
				{
					if(q[i].z>y && belong(q[i].x,x)) last++;
				}
			write(last),putchar('\n');
		}else
		if(op==1)
		{
			q[++num]=(data){1,x,w[x],y};
			w[x]=y;
			if(num==lim) rebuild();
		}else
		{
			q[++num]=(data){2,++n,x,y};
			insert(x,n);
			insert(n,x);
			fa[n][0]=x;
			dep[n]=dep[x]+1;
			w[n]=y;
			for(int i=1;i<16;i++) fa[n][i]=fa[fa[n][i-1]][i-1];
			if(num==lim) rebuild();
		}
	}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值