天天爱跑步[NOIP2016-Day1-T2](树上差分+桶)

文章目录

题目

Luogu
题目大意
一棵树, n n n 个节点,每个点有观察员, 观察员 i i i w i w_i wi 观察一次,有 m m m 个人跑步,第 i i i 个人从 s i s_i si t i t_i ti ,到达后下一秒消失, m m m 个人同时出发,边长均为 1 1 1,问每个观察员观察的人数?
1 ≤ n , m ≤ 3 ⋅ 1 0 5 1\le n,m\le 3\cdot10^5 1n,m3105

思路

看着表来打
在这里插入图片描述
25   o p t : 25\ opt: 25 opt:
考虑一些比较暴力的做法
预处理出每个人走的路径然后一秒一秒走,观察员按照时间排序,被观察到暴力加++
时间复杂度 O ( n ( n + m ) ) O(n(n+m)) O(n(n+m))

6 ∼ 20 6\sim20 620 发现暴力++的方法已经不行了,这就提示我们要用一些比较快的加法
差分,前缀和,线段树

15   o p t : 15\ opt: 15 opt:
6 ∼ 8 6\sim 8 68 我们可以这样做
在所有 s i s_i si 处分往左右走++
把跑步的人看作 ( t i − s i + 1 , s i , l ∣ r ) (t_i-s_i+1,s_i,l|r) (tisi+1si,lr) 的修改点
观察者看作 ( w i , i , 0 ) (w_i,i,0) (wi,i,0) 的询问点
两个弄在一起按时间排序,依次处理
跑步的人拿出来后再对应桶里面–
询问的人答案贡献就是 L [ i − w i ] + R [ i + w i ] L[i-w_i]+R[i+w_i] L[iwi]+R[i+wi]

20   o p t : 20\ opt: 20 opt: 所有 s i = 1 s_i=1 si=1
考虑 w i w_i wi 会有答案当且仅当 w i = d e p i w_i=dep_i wi=depi
将跑步人的 t i t_i ti 处++
然后对于 w i = d e p i w_i=dep_i wi=depi 答案为子树和

20   o p t : 20\ opt: 20 opt: 所有 t i = 1 t_i=1 ti=1
模仿 s i = 1 s_i=1 si=1 的思路,假设第 i i i 个人跑步时间为 l i = d e p s i l_i=dep_{s_i} li=depsi 发现 w i + d e p i = l j w_i+dep_i=l_j wi+depi=lj
时候会对答案产生贡献
发现这里 w i + d e p i w_i+dep_i wi+depi 为定值
联系链的情况,想到了用桶完成操作
访问一个点记录 b [ w i + d e p i ] b[w_i+dep_i] b[wi+depi]
更新时将人挂到对应 s i s_i si 上面 然后 b [ l i ] + + b[l_i]++ b[li]++
子树 DFS 完出来后 b [ w i + d e p i ] b[w_i+dep_i] b[wi+depi] 的增量就是答案

最后 20   o p t : 20\ opt: 20 opt: 正解
上一种情况给了我们很大的启发
将一个人走的路径拆为向上走和向下走, < s i , l c a i > <s_i,lca_i> <si,lcai> < s o n l c a i , t i > <sonlca_i,t_i> <sonlcai,ti>
这里 s o n l c a i sonlca_i sonlcai 指包含 t i t_i ti 的那个儿子
在这里插入图片描述
对于向上走发现对答案贡献只有当 w i + d e p i = d e p s i w_i+dep_i=dep_{s_i} wi+depi=depsi 成立
对于向下走发现对答案贡献只有当 w i = l i − ( d e p t i − d e p i ) w_i=l_i-(dep_{t_i}-dep_i) wi=li(deptidepi) 成立,
变形可得 w i − d e p i = l i − d e p t i w_i-dep_i=l_i-dep_{t_i} widepi=lidepti
可以模仿链建立两种不同的询问方式
然后是点差的常规操作
s i s_i si 挂对应 d e p s i dep_{s_i} depsi f l c a flca flca 挂对应 d e p s i dep_{s_i} depsi
t i t_i ti 挂对应 l i − d e p t i l_i-dep_{t_i} lidepti l c a lca lca 挂对应 l i − d e p t i l_i-dep_{t_i} lidepti
DFS遍历完子树后统计即可

代码

#include<set>
#include<map>
#include<cmath>
#include<deque>
#include<stack>
#include<ctime>
#include<queue>
#include<vector>
#include<cstdio>
#include<cstring>
#include<climits>
#include<iostream>
#include<algorithm>
#define LL long long
#define ULL unsigned long long
using namespace std;
int read(){
	int f=1,x=0;char c=getchar();
	while(c<'0'||'9'<c){if(c=='-')f=-1;c=getchar();}
	while('0'<=c&&c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
	return f*x;
}
#define MAXN 300000
#define INF 0x3f3f3f3f
struct Edge{
	int v,nxt;
}edge[2*MAXN+5];
int ecnt,head[MAXN+5];
inline void Addedge(int u,int v){
	edge[++ecnt]=(Edge){v,head[u]},head[u]=ecnt;
	edge[++ecnt]=(Edge){u,head[v]},head[v]=ecnt;
	return ;
}
int n,m,lg2,dep[MAXN+5],fa[MAXN+5][20];
int w[MAXN+5],s[MAXN+5],t[MAXN+5];
void dfs(int u){
	for(int i=head[u];i;i=edge[i].nxt){
		int v=edge[i].v;
		if(v==fa[u][0]) continue;
		fa[v][0]=u,dep[v]=dep[u]+1,dfs(v);
	}
	return ;
}
void Prepare(){
	for(int j=1;(1<<j)<=n;j++)
		for(int i=1;i<=n;i++)
			fa[i][j]=fa[fa[i][j-1]][j-1];
	return ;
}
int Lca(int u,int v){
	if(dep[u]<dep[v]) swap(u,v);
	int d=dep[u]-dep[v];
	for(int j=lg2;j>=0;j--)
		if(d&(1<<j))
			u=fa[u][j];
	if(u==v)
		return u;
	for(int j=lg2;j>=0;j--)
		if(fa[u][j]!=fa[v][j])
			u=fa[u][j],v=fa[v][j];
	return fa[u][0];
}
vector<int> Add1[MAXN+5],Sub1[MAXN+5],Add2[MAXN+5],Sub2[MAXN+5];
int c1[2*MAXN+5],c2[2*MAXN+5],ans[MAXN+5];
void Cal(int u,int fa){
	int tmp1=c1[dep[u]+w[u]],tmp2=c2[w[u]-dep[u]+n];
	for(int i=head[u];i;i=edge[i].nxt){
		int v=edge[i].v;
		if(v==fa) continue;
		Cal(v,u);
	}
	for(int i=0;i<int(Add1[u].size());i++) c1[Add1[u][i]]++;
	for(int i=0;i<int(Sub1[u].size());i++) c1[Sub1[u][i]]--;
	for(int i=0;i<int(Add2[u].size());i++) c2[Add2[u][i]+n]++;
	for(int i=0;i<int(Sub2[u].size());i++) c2[Sub2[u][i]+n]--;
	ans[u]=c1[dep[u]+w[u]]+c2[w[u]-dep[u]+n]-tmp1-tmp2;
	return ;
}
int main(){
	n=read(),m=read(),lg2=(int)log2(n);
	for(int i=1;i<n;i++){
		int u=read(),v=read();
		Addedge(u,v);
	}
	for(int i=1;i<=n;i++)
		w[i]=read();
	for(int i=1;i<=m;i++)
		s[i]=read(),t[i]=read();
	dfs(1);
	Prepare();
	for(int i=1;i<=m;i++){
		int lca=Lca(s[i],t[i]);
		Add1[s[i]].push_back(dep[s[i]]);
		Sub1[fa[lca][0]].push_back(dep[s[i]]);
		int Len=dep[s[i]]+dep[t[i]]-2*dep[lca];
		Add2[t[i]].push_back(Len-dep[t[i]]);
		Sub2[lca].push_back(Len-dep[t[i]]);
	}
	Cal(1,0);
	for(int i=1;i<=n;i++)
		printf("%d",ans[i]),putchar(i==n?'\n':' ');
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值