BZOJ 3626 LCA 树链剖分

题目大意:

给出一个n个节点的有根树(编号为0到n-1,根节点为0)。一个点的深度定义为这个节点到根的距离+1。
设dep[i]表示点i的深度,LCA(i,j)表示i与j的最近公共祖先。
有q次询问,每次询问给出l r z,求sigma_{l<=i<=r}dep[LCA(i,z)]。
(即,求在[l,r]区间内的每个节点i与z的最近公共祖先的深度之和)

这题看见了直接卡壳。。。然后看了题解才搞懂要怎么写。。。

直接复制gconeice的题解吧 

显然,暴力求解的复杂度是无法承受的。
考虑这样的一种暴力,我们把 z 到根上的点全部打标记,对于 l 到 r 之间的点,向上搜索到第一个有标记的点求出它的深度统计答案。观察到,深度其实就是上面有几个已标记了的点(包括自身)。所以,我们不妨把 z 到根的路径上的点全部 +1,对于 l 到 r 之间的点询问他们到根路径上的点权和。仔细观察上面的暴力不难发现,实际上这个操作具有叠加性,且可逆。也就是说我们可以对于 l 到 r 之间的点 i,将 i 到根的路径上的点全部 +1, 转而询问 z 到根的路径上的点(包括自身)的权值和就是这个询问的答案。把询问差分下,也就是用 [1, r] − [1, l − 1] 来计算答案,那么现在我们就有一个明显的解法。从 0 到 n − 1 依次插入点 i,即将 i 到根的路径上的点全部+1。离线询问答案即可。我们现在需要一个数据结构来维护路径加和路径求和,显然树链剖分或LCT 均可以完成这个任务。树链剖分的复杂度为 O((n + q)· log n · log n),LCT的复杂度为 O((n + q)· log n),均可以完成任务。至此,题目已经被我们完美解决。

写的很详细,我就不累述了

同样是14年的省选,同样是树链剖分,为啥辽宁省的就如此牛B,吉林省的就是水题。。。差距好大0.0

取模的话数字不是很大 可以开long long 最后取一下模就好了 中途不要取模 弄成负的WA了我一下午0.0 又一下午0.0 又一下午0.0 啊啊啊啊啊啊啊啊啊

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ls tree[p].lson
#define rs tree[p].rson
#define root 0,1,n
#define M 50100
using namespace std;
typedef long long ll;
struct abcd{
	int to,next;
}table[M];
int head[M],tot;
struct Query{
	int num,z,pos;
	ll ans;
}q[M<<1];
struct Tree{
	ll num,mark;
	int lson,rson;
}tree[M<<1];int treetot;
int n,m,fa[M],son[M],dpt[M],siz[M],pos[M],top[M],cnt;
bool operator < (Query x,Query y)
{
	return x.num < y.num ;
}
int cmp(Query x,Query y)
{
	if(x.pos==y.pos)
		return x.num < y.num ;
	return x.pos < y.pos ;
}
void add(int x,int y)
{
	table[++tot].to=y;
	table[tot].next=head[x];
	head[x]=tot;
}
void Build_tree(int p,int x,int y)
{
	int mid=x+y>>1;
	if(x==y)
		return ;
	ls=++treetot;
	rs=++treetot;
	Build_tree(ls,x,mid);
	Build_tree(rs,mid+1,y);
}
void change(int p,int x,int y,int l,int r)
{
	int mid=x+y>>1;
	if(x==l&&y==r)
	{
		tree[p].num+=(r-l+1);
		tree[p].mark++;
		return ;
	}
	if(tree[p].mark)
	{
		tree[ls].num+=(mid-x+1)*tree[p].mark;
		tree[rs].num+=(y-mid)*tree[p].mark;
		tree[ls].mark+=tree[p].mark;
		tree[rs].mark+=tree[p].mark;
		tree[p].mark=0;
	}
	if(r<=mid) change(ls,x,mid,l,r);
	else if(l>mid) change(rs,mid+1,y,l,r);
	else change(ls,x,mid,l,mid),change(rs,mid+1,y,mid+1,r);
	tree[p].num=tree[ls].num+tree[rs].num;
}
ll getans(int p,int x,int y,int l,int r)
{
	int mid=x+y>>1;
	if(x==l&&y==r)
		return tree[p].num;
	if(tree[p].mark)
	{
		tree[ls].num+=(mid-x+1)*tree[p].mark;
		tree[rs].num+=(y-mid)*tree[p].mark;
		tree[ls].mark+=tree[p].mark;
		tree[rs].mark+=tree[p].mark;
		tree[p].mark=0;
	}
	if(r<=mid) return getans(ls,x,mid,l,r);
	if(l>mid) return getans(rs,mid+1,y,l,r);
	return getans(ls,x,mid,l,mid)+getans(rs,mid+1,y,mid+1,r);
}
void dfs1(int x)
{
	int i;
	dpt[x]=dpt[fa[x]]+1;
	siz[x]=1;
	for(i=head[x];i;i=table[i].next)
	{
		fa[table[i].to]=x;
		dfs1(table[i].to);
		if(siz[table[i].to]>siz[son[x]])
			son[x]=table[i].to;
		siz[x]+=siz[table[i].to];
	}
}
void dfs2(int x)
{
	int i;
	if(son[fa[x]]==x)
		top[x]=top[fa[x]];
	else
	{
		top[x]=x;
		for(i=x;i;i=son[i])
			pos[i]=++cnt;
	}
	for(i=head[x];i;i=table[i].next)
		dfs2(table[i].to);
}
void update(int x)
{
	int fx=top[x];
	while(x)
	{
		change(root,pos[fx],pos[x]);
		x=fa[fx];
		fx=top[x];
	}
}
ll query(int x)
{
	ll re=0;
	int fx=top[x];
	while(x)
	{
		re+=getans(root,pos[fx],pos[x]);
		x=fa[fx];
		fx=top[x];
	}
	return re;
}
int main()
{
	int i,j,x,y,z;
	cin>>n>>m;
	for(i=2;i<=n;i++)
		scanf("%d",&x),add(x+1,i);
	dfs1(1);
	dfs2(1);
	for(i=1;i<=m;i++)
	{
		scanf("%d%d%d",&x,&y,&z);
		x++;y++;z++;
		q[i+i-1].num=x-1;
		q[i+i-1].z=z;
		q[i+i-1].pos=i;
		q[i<<1].num=y;
		q[i<<1].z=z;
		q[i<<1].pos=i;
	}
	sort(q+1,q+m+m+1);
	Build_tree(root);
	j=1;
	for(i=0;i<=n;i++)
	{
		update(i);
		for(;q[j].num==i;j++)
			q[j].ans=query(q[j].z);
	}
	sort(q+1,q+m+m+1,cmp);
	for(i=1;i<=m;i++)
		printf("%lld\n", ( q[i<<1].ans - q[i+i-1].ans ) % 201314 );
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值