Union on Tree(毒瘤数据结构题)(点分树+虚树+树点覆盖去重)

There is a country whose road system is a tree, the nodes in the tree represent cities and the edge is the road between them, and each edge is of unit length. For safety, the government should put guards to protect all cities. But the government is in shortage of money. So they might not be able to protect all cites.

You're concerned about the country's safety, so you get the information of the guards. And you know that on the i-th day, there are k[i] guards on the road system, and j-th guard is on node a[j] and can protect every node whose distance to a[j] is not larger than r[j]. And you want to know how many nodes in the road system is protected by at least one guard.

Input

The first line contains an integer n, denoting the number of the cities.

Then n-1 lines follows, each line contains 2 integers a and b, denoting there is an edge between city a and b (1-based index).

The next line contains an integer Q, denoting the number of the days.

Then Q lines's description follows, each contains an integer k, denoting the number of guards in that day, and k integer pairs a[i],r[i] denoting the guards's information, for simplicity, no node will have more than 1 guard.

Constraints

  • 1 ≤ n, Q ≤ 50000.
  • The total number of guards in the queries ≤ 500000.

 

 

Output

For each day, output the answer in one line.

Example

Input:
20
1 2
1 3
1 4
4 5
4 6
2 7
4 8
5 9
7 10
2 11
9 12
8 13
1 14
12 15
9 16
7 17
12 18
1 19
6 20
5
2 9 3 12 5 
3 3 3 4 1 11 5 
3 3 3 10 4 19 2 
2 3 4 10 2 
5 5 4 11 2 16 2 18 1 19 2
Output:
16
16
13
16
18

 

题解

毒瘤题,写了我一晚上555555

查询一个点范围r内的点数可以建立点分树在线查询

然后对询问的点建立虚树

由于虚树上有些点有范围,有些点没有范围

我们需要统一一下,就把所有点的范围都设为它周围的点到它之后还能延伸出的最长长度

这样处理并不会影响答案,因为每个点所包含的点集一定会被某一个点(可能是它本身)的点集包含

这样处理有什么好处呢?

我们会发现能覆盖一个点的点集会在虚树上形成一个连通块

而树上的连通块一定也是一棵树,满足点-边=1

所以我们可以先把虚树上每个点的答案统计进来

一定会有一些点被重复算了

而每个点被计算的次数就是能覆盖它的点集的大小

所以我们只需要枚举虚树上的边,把两边端点重复覆盖的点数减去即可

这样就可以保证每个点都只被统计了一次

怎么求两边端点重复覆盖的点数呢?

我们可以找到虚树边上覆盖部分在原树上的中点,利用它覆盖的范围来求重复覆盖的点数

(如图,黑点为虚树点,红点为原树的点)

但是我们发现有些时候并不能找到中点

我们可以在建立原树的时候在两个点之间插入一个新点,表示这条边,在点分树内不统计它的答案即可

代码:(4KB警告)(考虑到此题没有修改操作,点分树可以不用套动态开点线段树,直接用一个vector记录前缀和)

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
inline int gi()
{
	char c;int num=0,flg=1;
	while((c=getchar())<'0'||c>'9')if(c=='-')flg=-1;
	while(c>='0'&&c<='9'){num=num*10+c-48;c=getchar();}
	return num*flg;
}
#define N 100005
#define LOG 16
const int INF=0x3f3f3f3f;
int n,m;
int fir[N],to[2*N],nxt[2*N],cnt;
void adde(int a,int b)
{
	to[++cnt]=b;nxt[cnt]=fir[a];fir[a]=cnt;
	to[++cnt]=a;nxt[cnt]=fir[b];fir[b]=cnt;
	//printf("%d %d\n",a,b);
}
int f[N][LOG+2],dep[N],dfn[N],dc;
void dfs(int u)
{
	dfn[u]=++dc;dep[u]=dep[f[u][0]]+1;
	for(int v,p=fir[u];p;p=nxt[p])
		if((v=to[p])!=f[u][0]){f[v][0]=u;dfs(v);}
}
namespace DFS{
	vector<int> T1[N],T2[N];
	int dfa[N],dep[N];
	int nrt,all,tmpsiz[N];bool vis[N];
	void findrt(int u,int ff){
		int mx=0;tmpsiz[u]=1;
		for(int v,p=fir[u];p;p=nxt[p]){
			if((v=to[p])!=ff&&!vis[v]){
				findrt(v,u);
				tmpsiz[u]+=tmpsiz[v];
				mx=max(mx,tmpsiz[v]);
			}
		}
		mx=max(mx,all-tmpsiz[u]);
		if(2*mx<=all)nrt=u;
	}
	int getrt(int u,int sz){
		all=sz;nrt=-INF;
		findrt(u,0);return nrt;
	}
	int mx1,mx2,tmp[N],dfn,dis[N][LOG+2];
	void pre(int u,int d,int ff){
		tmp[++dfn]=u;
		mx1=max(mx1,dis[u][d]);
		mx2=max(mx2,dis[u][d-1]);
		tmpsiz[u]=1;
		for(int v,p=fir[u];p;p=nxt[p]){
			if(!vis[v=to[p]]&&v!=ff){
				dis[v][d]=dis[u][d]+1;
				pre(v,d,u);
				tmpsiz[u]+=tmpsiz[v];
			}
		}
	}
	void DFZ(int u,int d){
		dep[u]=d;
		dfn=mx1=mx2=0;vis[u]=1;pre(u,d,0);
		for(int i=0;i<=mx1;i++)T1[u].push_back(0);
		for(int i=0;i<=mx2;i++)T2[u].push_back(0);
		for(int i=1;i<=dfn;i++){
			int x=tmp[i];
			if(x<=((n+1)>>1)){
				T1[u][dis[x][d]]++;
				T2[u][dis[x][d-1]]++;
			}
		}
		for(int i=1;i<=mx1;i++)T1[u][i]+=T1[u][i-1];
		for(int i=1;i<=mx2;i++)T2[u][i]+=T2[u][i-1];
		for(int v,p=fir[u];p;p=nxt[p])
			if(!vis[v=to[p]]){
				dfa[v=getrt(v,tmpsiz[v])]=u;
				DFZ(v,d+1);
			}
	}
	void work(){DFZ(getrt(1,n),1);}
	int query(int u,int k){
		if(k==-1)return 0;
		int ret=0;
		for(int t=u;t;t=dfa[t]){
			if(dfa[t]&&k-dis[u][dep[t]-1]>=0)
				ret-=T2[t][min((int)T2[t].size()-1,k-dis[u][dep[t]-1])];
			if(k-dis[u][dep[t]]>=0)
				ret+=T1[t][min((int)T1[t].size()-1,k-dis[u][dep[t]])];
		}
		return ret;
	}
}
int getk(int x,int k){for(int i=LOG;i>=0;i--)if(k&(1<<i))x=f[x][i];return x;}
int getd(int x,int d){return getk(x,dep[x]-d);}
int LCA(int x,int y)
{
	if(dep[x]<dep[y])swap(x,y);
	x=getd(x,dep[y]);
	if(x==y)return x;
	for(int i=LOG;i>=0;i--)
		if(f[x][i]!=f[y][i])
			x=f[x][i],y=f[y][i];
	return f[x][0];
}
bool cmp(int x,int y){return dfn[x]<dfn[y];}
int tmp[N],R[N],dcon,stk[N],top;bool ont[N];
void clean()
{
	for(int i=1;i<=dcon;i++)
		fir[tmp[i]]=0,ont[tmp[i]]=0,R[tmp[i]]=-1;
	cnt=0;
}
void build()
{
	//printf("build:\n");
	sort(tmp+1,tmp+dcon+1,cmp);
	top=0;
	int tcon=dcon,lca,x;
	if(tmp[1]!=1)tmp[++dcon]=1,stk[++top]=1;
	for(int i=1;i<=tcon;i++){
		ont[x=tmp[i]]=1;
		if(top<=1||(lca=LCA(stk[top],x))==stk[top]){
			stk[++top]=x;
			continue;
		}
		while(top>1&&dep[stk[top-1]]>=dep[lca]){
			adde(stk[top-1],stk[top]);
			top--;
		}
		if(lca!=stk[top]){
			adde(lca,stk[top]);
			stk[top]=lca;tmp[++dcon]=lca;
		}
		stk[++top]=x;
	}
	while(top>1){
		adde(stk[top-1],stk[top]);
		top--;
	}
}
void dfs1(int u,int ff)
{
	for(int v,p=fir[u];p;p=nxt[p])
		if((v=to[p])!=ff){
			dfs1(v,u);
			R[u]=max(R[u],R[v]-(dep[v]-dep[u]));
		}
}
void dfs2(int u,int ff)
{
	for(int v,p=fir[u];p;p=nxt[p])
		if((v=to[p])!=ff){
			R[v]=max(R[u]-(dep[v]-dep[u]),R[v]);
			dfs2(v,u);
		}
}
int ans;
void solve(int u,int ff)
{
	ans+=DFS::query(u,R[u]);
	for(int v,p=fir[u];p;p=nxt[p]){
		if((v=to[p])!=ff){
			if(dep[u]+R[u]>=dep[v]-R[v]){
				int z=(dep[u]+R[u]+dep[v]-R[v])>>1,r=(dep[u]+R[u]-(dep[v]-R[v]))>>1;
				z=getd(v,z);
				ans-=DFS::query(z,r);
			}
			solve(v,u);
		}
	}
}
int main()
{
	int i,j,u,v;
	n=gi();
	for(i=1;i<n;i++){u=gi();v=gi();adde(u,n+i);adde(n+i,v);}
	n=2*n-1;
	dfs(1);
	for(j=1;j<=LOG;j++)for(i=1;i<=n;i++)f[i][j]=f[f[i][j-1]][j-1];
	DFS::work();
	memset(fir,0,sizeof(fir));cnt=0;
	memset(R,-1,sizeof(R));
	m=gi();
	while(m--){
		dcon=gi();
		for(i=1;i<=dcon;i++){tmp[i]=gi();R[tmp[i]]=gi()*2;}
		ans=0;build();dfs1(1,0);dfs2(1,0);solve(1,0);clean();
		printf("%d\n",ans);
	}
}

 

 

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值