BZOJ3653 谈笑风生【长链剖分】

题目描述:

设T 为一棵有根树,我们做如下的定义:
• 设a和b为T 中的两个不同节点。如果a是b的祖先,那么称“a比b不知道
高明到哪里去了”。
• 设a 和 b 为 T 中的两个不同节点。如果 a 与 b 在树上的距离不超过某个给定
常数x,那么称“a 与b 谈笑风生”。
给定一棵n个节点的有根树T,节点的编号为1 到 n,根节点为1号节点。你需
要回答q 个询问,询问给定两个整数p和k,问有多少个有序三元组(a;b;c)满足:

  1. a、b和 c为 T 中三个不同的点,且 a为p 号节点;
  2. a和b 都比 c不知道高明到哪里去了;
  3. a和b 谈笑风生。这里谈笑风生中的常数为给定的 k。

n,q<=300000

题目分析:

显然b要么是a的祖先要么是a的儿子,是a的祖先可以直接计算,主要考虑a的儿子的情况。
答案可以看出是子树中深度在k以内的size-1之和。

可以利用dfs序和深度进行二维数点,离线BIT或者在线主席树。
可以树形DP,离线长链剖分或者在线动态开点线段树合并,或者点分治

这里说一下长链剖分,用 f [ u ] [ i ] f[u][i] f[u][i]表示深度为 i i i的答案。
会发现询问需要前缀和,但是更新的时候不好维护前缀和(方便一点的都要log),原因是继承的是长链,前缀和需要更新整个长儿子的深度,反过来想后缀和就只需要更新新加入的儿子的深度了,DP方程没有大变动,就加上一句 f [ u ] [ 0 ] + = f [ v ] [ 0 ] f[u][0]+=f[v][0] f[u][0]+=f[v][0] f f f的意义就是后缀和了。

长链剖分指针的写法,是根据最大深度分配内存,由时间复杂度的证明可以看出空间是O(n)的。

Code:

#include<bits/stdc++.h>
#define maxn 300005
#define LL long long
using namespace std;
int n,m,siz[maxn],dep[maxn],mxd[maxn],son[maxn];
LL Sum[maxn],*f[maxn],*st=Sum,ans[maxn];
vector<int>G[maxn];
vector<pair<int,int> >q[maxn];
void dfs1(int u,int ff){
	mxd[u]=dep[u]=dep[ff]+1,siz[u]=1;
	for(int i=G[u].size()-1,v;i>=0;i--) if((v=G[u][i])!=ff){
		dfs1(v,u),mxd[u]=max(mxd[u],mxd[v]),siz[u]+=siz[v];
		if(mxd[v]>mxd[son[u]]) son[u]=v;
	}
}
void dfs(int u,int ff){
	f[u][0]=siz[u]-1;
	if(son[u]) f[son[u]]=f[u]+1,dfs(son[u],u),f[u][0]+=f[son[u]][0];
	for(int i=G[u].size()-1,v;i>=0;i--) if((v=G[u][i])!=ff&&v!=son[u]){
		f[v]=st,st+=mxd[v]-dep[u],dfs(v,u);
		for(int j=0;j<=mxd[v]-dep[v];j++) f[u][j+1]+=f[v][j];
		f[u][0]+=f[v][0];
	}
	for(int i=q[u].size()-1,id,k;i>=0;i--){
		ans[id=q[u][i].second]=1ll*(siz[u]-1)*min(dep[u]-1,k=q[u][i].first);
		ans[id]+=f[u][1]-(k>=mxd[u]-dep[u]?0:f[u][k+1]);
	}
}
int main()
{
	int x,y;
	scanf("%d%d",&n,&m);
	for(int i=1;i<n;i++) scanf("%d%d",&x,&y),G[x].push_back(y),G[y].push_back(x);
	for(int i=1;i<=m;i++) scanf("%d%d",&x,&y),q[x].push_back(make_pair(y,i));
	dfs1(1,0),f[1]=st,st+=mxd[1],dfs(1,0);
	for(int i=1;i<=m;i++) printf("%lld\n",ans[i]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值