【BZOJ3589】动态树 树链剖分+线段树

【BZOJ3589】动态树

Description

别忘了这是一棵动态树, 每时每刻都是动态的. 小明要求你在这棵树上维护两种事件
事件0:
这棵树长出了一些果子, 即某个子树中的每个节点都会长出K个果子.
事件1:
小明希望你求出几条树枝上的果子数. 一条树枝其实就是一个从某个节点到根的路径的一段. 每次小明会选定一些树枝, 让你求出在这些树枝上的节点的果子数的和. 注意, 树枝之间可能会重合, 这时重合的部分的节点的果子只要算一次.

Input

第一行一个整数n(1<=n<=200,000), 即节点数.
接下来n-1行, 每行两个数字u, v. 表示果子u和果子v之间有一条直接的边. 节点从1开始编号.
在接下来一个整数nQ(1<=nQ<=200,000), 表示事件.
最后nQ行, 每行开头要么是0, 要么是1.
如果是0, 表示这个事件是事件0. 这行接下来的2个整数u, delta表示以u为根的子树中的每个节点长出了delta个果子.
如果是1, 表示这个事件是事件1. 这行接下来一个整数K(1<=K<=5), 表示这次询问涉及K个树枝. 接下来K对整数u_k, v_k, 每个树枝从节点u_k到节点v_k. 由于果子数可能非常多, 请输出这个数模2^31的结果.

Output

对于每个事件1, 输出询问的果子数.

Sample Input

5
1 2
2 3
2 4
1 5
3
0 1 1
0 2 3
1 2 3 1 1 4

Sample Output

13

HINT

 1 <= n <= 200,000, 1 <= nQ <= 200,000, K = 5.

生成每个树枝的过程是这样的:先在树中随机找一个节点, 然后在这个节点到根的路径上随机选一个节点, 这两个节点就作为树枝的两端.

题解:话说这题做法好多啊,容斥、打标记、黑科技。。。是数据比较良心的缘故?

我的做法也类似于黑科技吧?因为我们在查询时,会访问到线段树上许多个可能会重叠的区间,那么我给线段树的每个节点都增加一个时间标记。当我们在线段树找到一个合法的整区间后,还要继续判断这个区间的在当前的时间是否打过标记,然后继续向下查找。这样做的时间复杂度我也不太清楚,大概O(nlog3n)差不多吧?

#include <cstdio>
#include <cstring>
#include <iostream>
#define lson x<<1
#define rson x<<1|1
using namespace std;
const int maxn=200010;
int n,m,cnt,now,ans;
int to[maxn<<1],next[maxn<<1],head[maxn],v[maxn],fa[maxn],dep[maxn],siz[maxn],son[maxn],top[maxn];
int tt[maxn<<2],ts[maxn<<2],s[maxn<<2],tm[maxn<<2],tn[maxn<<2],p[maxn],q[maxn];
void add(int a,int b)
{
	to[cnt]=b,next[cnt]=head[a],head[a]=cnt++;
}
void dfs1(int x)
{
	siz[x]=1;
	for(int i=head[x];i!=-1;i=next[i])
	{
		if(to[i]!=fa[x])
		{
			fa[to[i]]=x,dep[to[i]]=dep[x]+1,dfs1(to[i]),siz[x]+=siz[to[i]];
			if(siz[to[i]]>siz[son[x]])	son[x]=to[i];
		}
	}
}
void dfs2(int x,int tp)
{
	top[x]=tp,p[x]=++p[0];
	if(son[x])	dfs2(son[x],tp);
	for(int i=head[x];i!=-1;i=next[i])
		if(to[i]!=fa[x]&&to[i]!=son[x])
			dfs2(to[i],to[i]);
	q[x]=p[0];
}
void pushdown(int l,int r,int x)
{
	if(tt[x])	tt[lson]=tt[rson]=tm[lson]=tm[rson]=tn[lson]=tn[rson]=tt[x],tt[x]=0;
	if(ts[x])
	{
		int mid=l+r>>1;
		ts[lson]+=ts[x],ts[rson]+=ts[x],s[lson]+=(mid-l+1)*ts[x],s[rson]+=(r-mid)*ts[x],ts[x]=0;
	}
}
void pushup(int x)
{
	tm[x]=max(tm[lson],tm[rson]);
	tn[x]=min(tn[lson],tn[rson]);
	s[x]=s[lson]+s[rson];
}
void updata(int l,int r,int x,int a,int b,int c)
{
	if(a<=l&&r<=b)
	{
		s[x]+=(r-l+1)*c,ts[x]+=c;
		return ;
	}
	pushdown(l,r,x);
	int mid=l+r>>1;
	if(a<=mid)	updata(l,mid,lson,a,b,c);
	if(b>mid)	updata(mid+1,r,rson,a,b,c);
	pushup(x);
}
void q2(int l,int r,int x)
{
	if(tm[x]<now)
	{
		ans+=s[x],tm[x]=tn[x]=tt[x]=now;
		return ;
	}
	pushdown(l,r,x);
	int mid=l+r>>1;
	if(tn[lson]<now)	q2(l,mid,lson);
	if(tn[rson]<now)	q2(mid+1,r,rson);
	pushup(x);
}
void q1(int l,int r,int x,int a,int b)
{
	if(a<=l&&r<=b)
	{
		if(tn[x]<now)	q2(l,r,x);
		return ;
	}
	pushdown(l,r,x);
	int mid=l+r>>1;
	if(a<=mid)	q1(l,mid,lson,a,b);
	if(b>mid)	q1(mid+1,r,rson,a,b);
	pushup(x);
}
void ask(int x,int y)
{
	if(dep[x]<dep[y])	swap(x,y);
	while(top[x]!=top[y])
	{
		q1(1,n,1,p[top[x]],p[x]);
		x=fa[top[x]];
	}
	q1(1,n,1,p[y],p[x]);
}
int rd()
{
	int ret=0,f=1;	char gc=getchar();
	while(gc<'0'||gc>'9')	{if(gc=='-')f=-f;	gc=getchar();}
	while(gc>='0'&&gc<='9')	ret=ret*10+gc-'0',gc=getchar();
	return ret*f;
}
int main()
{
	n=rd();
	int i,j,a,b,c;
	memset(head,-1,sizeof(head));
	for(i=1;i<n;i++)	a=rd(),b=rd(),add(a,b),add(b,a);
	dep[1]=1,dfs1(1),dfs2(1,1);
	m=rd();
	for(i=1;i<=m;i++)
	{
		if(!rd())	a=rd(),updata(1,n,1,p[a],q[a],rd());
		else
		{
			c=rd(),now++,ans=0;
			for(j=1;j<=c;j++)	a=rd(),b=rd(),ask(a,b);
			printf("%d\n",ans&2147483647);
		}
	}
	return 0;
}

转载于:https://www.cnblogs.com/CQzhangyu/p/7088071.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值