View on GitHub

Wenchong Huang

旧博客,文章搬迁完后删除

返回首页 返回专题

【BZOJ3589】动态树 题解

BZOJ权限题,这里给一下题面:

Description

别忘了这是一棵动态树, 每时每刻都是动态的。 小明要求你在这棵树上维护两种事件:

事件0:

这棵树长出了一些果子, 即某个子树中的每个节点都会长出K个果子。

事件1:

小明希望你求出几条树枝上的果子数。 一条树枝其实就是一个从某个节点到根的路径的一段。 每次小明会选定一些树枝, 让你求出在这些树枝上的节点的果子数的和。

注意, 树枝之间可能会重合, 这时重合的部分的节点的果子只要算一次。

Input

第一行一个整数n(1≤n≤200,000), 即节点数。

接下来n−1行,每行两个数字u,v。 表示果子u和果子v之间有一条直接的边。节点从11开始编号。

在接下来一个整数nQ(1≤nQ≤200,000), 表示事件。

最后nQ行,每行开头要么是0,要么是1。

如果是0, 表示这个事件是事件0。 这行接下来的2个整数u,delta表示以u为根的子树中的每个节点长出了delta个果子。

如果是1, 表示这个事件是事件11。 这行接下来一个整数K(1≤K≤5), 表示这次询问涉及KK个树枝。 接下来K对整数uk,vk每个树枝从节点uk到节点vk。 由于果子数可能非常多, 请输出这个数模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

100%: 1≤N,Q≤200000, 1≤K≤5100%: 1≤N,Q≤200000, 1≤K≤5

题解

看到这题直接就想到了树链剖分+线段树维护

事件0是树剖的常规操作,只要计算出每个点dfs序里的in和out,就可以直接在线段树的相应区间加一个值了

事件1稍微麻烦一些

如果是只有两个节点,那也是常规操作,沿重链上跳到LCA,沿途求和即可

多个节点的话,可以考虑容斥,但那会比较麻烦

我们可以让线段树支持一个覆盖操作,询问时,沿重链上跳到LCA,沿途放标记,表示“需要这一段区间”,询问完成后要在根节点放一个标记,表示“不需要这一段区间”。处理起来有一些细节并不是很好描述,还是看代码吧

#include<bits/stdc++.h>
#define INF 0x7fffffff
using namespace std;

int read()
{
	int x=0;char c=getchar();
	while(!isdigit(c)) c=getchar();
	while(isdigit(c)) x=x*10+c-'0',c=getchar();
	return x;
}

const int N=200010;
struct Edge{int to,next;} e[N<<1];
int h[N],tot=0;
int fa[N],top[N],hson[N],size[N],dep[N];
int in[N],out[N],idex[N],dfn=0;

void add_edge(int u,int v)
{
	e[++tot].to=v;
	e[tot].next=h[u];
	h[u]=tot;
}

void dfs1(int u,int f)
{
	size[u]=1;int mx=0;
	for(int tmp=h[u];tmp;tmp=e[tmp].next)
	{
		int v=e[tmp].to;
		if(v==f) continue;
		dep[v]=dep[u]+1;
		fa[v]=u;dfs1(v,u);
		size[u]+=size[v];
		if(size[v]>mx) mx=size[v],hson[u]=v;
	}
}

void dfs2(int u,int tp)
{
	top[u]=tp;in[u]=++dfn;idex[dfn]=u;
	if(hson[u]) dfs2(hson[u],tp);
	for(int tmp=h[u];tmp;tmp=e[tmp].next)
	{
		int v=e[tmp].to;
		if(v==fa[u]||v==hson[u]) continue;
		dfs2(v,v);
	}
	out[u]=dfn;
}

int val[N<<2],sum[N<<2],add[N<<2],mdf[N<<2];

void pushdown(int o,int l,int r)
{
	if(add[o])
	{
		int mid=(l+r)/2;
		sum[o*2]+=add[o]*(mid-l+1);
		sum[o*2+1]+=add[o]*(r-mid);
		add[o*2]+=add[o];
		add[o*2+1]+=add[o];
		add[o]=0;
	}
	if(~mdf[o])
	{
		val[o*2]=sum[o*2]*mdf[o];
		val[o*2+1]=sum[o*2+1]*mdf[o];
		mdf[o*2]=mdf[o*2+1]=mdf[o];
		mdf[o]=-1;
	}
}

void maintain(int o)
{
	sum[o]=sum[o*2]+sum[o*2+1];
	val[o]=val[o*2]+val[o*2+1];
}

void Modify(int o,int l,int r,int nl,int nr,int ty)
{
	if(l>=nl&&r<=nr)
	{
		val[o]=ty*sum[o];
		mdf[o]=ty;
		return;
	}
	pushdown(o,l,r);
	int mid=(l+r)/2;
	if(nl<=mid) Modify(o*2,l,mid,nl,nr,ty);
	if(nr>mid) Modify(o*2+1,mid+1,r,nl,nr,ty);
	maintain(o);
}

void Add(int o,int l,int r,int nl,int nr,int k)
{
	if(l>=nl&&r<=nr)
	{
		sum[o]+=k*(r-l+1);
		add[o]+=k;
		return;
	}
	pushdown(o,l,r);
	int mid=(l+r)/2;
	if(nl<=mid) Add(o*2,l,mid,nl,nr,k);
	if(nr>mid) Add(o*2+1,mid+1,r,nl,nr,k);
	maintain(o);
}

void Modify(int u,int v)
{
	while(top[u]!=top[v])
	{
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		Modify(1,1,dfn,in[top[u]],in[u],1);
		u=fa[top[u]];
	}
	if(dep[u]>dep[v]) swap(u,v);
	Modify(1,1,dfn,in[u],in[v],1);
}

int main()
{
	int n=read(),u,v,opt,k;
	for(int i=1;i<n;i++)
	{
		u=read();v=read();
		add_edge(u,v);
		add_edge(v,u);
	}
	dep[1]=1;dfs1(1,0);dfs2(1,1);
	int Q=read();
	while(Q--)
	{
		opt=read();
		if(opt==0)
		{
			u=read();k=read();
			Add(1,1,n,in[u],out[u],k);
		}
		else
		{
			k=read();
			for(int i=1;i<=k;i++)
			{
				u=read();v=read();
				Modify(u,v);
			}
			printf("%d\n",val[1]&INF);
			Modify(1,1,n,1,n,0);
		}
	}
	return 0;
}