bzoj 3589: 动态树 (树链剖分+线段树)

3589: 动态树

Time Limit: 30 Sec   Memory Limit: 1024 MB
Submit: 451   Solved: 155
[ Submit][ Status][ Discuss]

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.


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

Source

By 佚名提供

[ Submit][ Status][ Discuss]


题解:树链剖分+线段树

这道题的操作1直接找到dfs序中该子树所对应的区间,进行线段树的区间加操作即可。

操作2,麻烦之处在于他给出的树枝存在重叠的情况,而我们不进行重复的计算。于是我们给线段树增加一个标记。就是区间覆盖标记,同时记录一下区间中被覆盖的总值,针对每个树枝用树链剖分求解答案(区间总值-区间被覆盖的值)即可,然后将该树枝对应的区间打标记,更新覆盖总值。树枝全部处理完成后,记得把覆盖标记清零。

这么做常数有点大,不过非常好写也比较好想。。。。

#include
#include
#include
#include
#include
#define  N 400003
#define LL long long 
using namespace std;
int m,n;
int point[N],next[N],tot,v[N],sz,a[N],b[N],cnt,l[N],r[N];
int size[N],deep[N],son[N],belong[N],f[N],pos[N];
LL tr[N*4],delta[N*4],rev[N*4],tr1[N*4],p;
void add(int x,int y)
{
	tot++; next[tot]=point[x]; point[x]=tot; v[tot]=y;
	tot++; next[tot]=point[y]; point[y]=tot; v[tot]=x;
}
void build(int x,int fa)
{
	size[x]=1; f[x]=fa; 
	for (int i=point[x];i;i=next[i])
	 if (v[i]!=fa)
	  {
	  	 deep[v[i]]=deep[x]+1; 
	  	 build(v[i],x);
	  	 size[x]+=size[v[i]];
	  	 if (size[v[i]]>size[son[x]])  son[x]=v[i];
	  }
	r[x]=cnt;
}
void dfs(int k,int chain)
{
	belong[k]=chain; pos[k]=++sz; l[k]=r[k]=sz;
	if (!son[k])  return;
	dfs(son[k],chain);
	for (int i=point[k];i;i=next[i])
	 if (son[k]!=v[i]&&v[i]!=f[k])
	  dfs(v[i],v[i]);
	r[k]=sz;
}
void update(int now)
{
	tr[now]=(tr[now<<1]+tr[now<<1|1])%p;
	tr1[now]=(tr1[now<<1]+tr1[now<<1|1])%p;
}
void pushdown(int now,int l,int r)
{
	int mid=(l+r)/2;
	if (delta[now])
	 {
	 	tr[now<<1]=(tr[now<<1]+(LL)(mid-l+1)*delta[now]%p)%p;
	 	tr[now<<1|1]=(tr[now<<1|1]+(LL)(r-mid)*delta[now]%p)%p;
	 	delta[now<<1]=(delta[now<<1]+delta[now]%p)%p;
	 	delta[now<<1|1]=(delta[now<<1|1]+delta[now]%p)%p;
	 	delta[now]=0;
	 }
	if (rev[now])
	{
	 if (rev[now]==1)
	  tr1[now<<1]=tr[now<<1],tr1[now<<1|1]=tr[now<<1|1];
	  else tr1[now<<1]=0,tr1[now<<1|1]=0;
	 rev[now<<1]=rev[now]; rev[now<<1|1]=rev[now];
	 rev[now]=0;
    }
}
void qjchange(int now,int l,int r,int ll,int rr,LL z)
{
	if (ll<=l&&r<=rr)
	 {
	 	tr[now]=(tr[now]+(LL)(r-l+1)*z%p)%p;
	 	delta[now]=(delta[now]+z%p)%p;
	 	return ;
	 }
	int mid=(l+r)/2;
	pushdown(now,l,r);
	if (ll<=mid) qjchange(now<<1,l,mid,ll,rr,z);
	if (rr>mid) qjchange(now<<1|1,mid+1,r,ll,rr,z);
	update(now);
}
void qjrev(int now,int l,int r,int ll,int rr,LL z)
{
	if (ll<=l&&r<=rr)
	 {
	 	if (z==1)  tr1[now]=tr[now]%p;
	 	else tr1[now]=0;
	 	rev[now]=z;
	 	return;
	 }
	int mid=(l+r)/2;
	pushdown(now,l,r);
	if (ll<=mid) qjrev(now<<1,l,mid,ll,rr,z);
	if (rr>mid) qjrev(now<<1|1,mid+1,r,ll,rr,z);
	update(now);
}
LL qjsum(int now,int l,int r,int ll,int rr)
{
	if (ll<=l&&r<=rr) return (tr[now]-tr1[now])%p;
	int mid=(l+r)/2; 
	pushdown(now,l,r);
	LL ans=0;
	if (ll<=mid) ans+=qjsum(now<<1,l,mid,ll,rr)%p;
	if (rr>mid) ans+=qjsum(now<<1|1,mid+1,r,ll,rr)%p;
	return ans%p;
}
LL solve(int x,int y)
{
	LL ans=0;
	while (belong[x]!=belong[y])
	 {
	 	if (deep[belong[x]]deep[y])  swap(x,y);
	ans+=qjsum(1,1,n,pos[x],pos[y])%p;
	qjrev(1,1,n,pos[x],pos[y],1);
	return ans;
}
void solve2(int x,int y)
{
	while (belong[x]!=belong[y])
	 {
	 	if (deep[belong[x]]deep[y]) swap(x,y);
	qjrev(1,1,n,pos[x],pos[y],-1);
}
int main()
{
	freopen("a.in","r",stdin);
	freopen("my.out","w",stdout);
	scanf("%d",&n); p=2147483648LL;
	for (int i=1;i


你可能感兴趣的:(线段树,树链剖分)