[P1600][NOIP2016]天天爱跑步(lca+桶+差分)

填一下退役前(被我遗忘)的坑……

读完题后首先想到的模拟过程,大概就是对于每个玩家的路径,一个点一个点地验证能否被观测到,把贡献加给该点。

要想写部分分的话,看到有一些数据s=1,也就是从根节点出发,那么只要路径上的点的深度(假设根节点深度为0)等于w值即可,于是可以给我们一个思路,一条路径上的点i能否被观测到,可由dep[i]和w[i]与这条路径的关系确定。

那么关系式长什么样呢?显然应该分成两部分考虑,由s到lca和由lca到t。稍微画几个图推一推可以发现,前半段观测条件为dep[i]+w[i]=dep[s],后半段为w[i]-dep[i]=dis[s,t]-dep[t]=dep[s]-2*dep[lca]。dep数组和lca都很好求。此时发现对于一条路径上所有点,等式右边的值都是确定的,可以提前求好,问题在于这条路径会对哪些点产生影响,把每条路径上每个点枚举一边就又回去了,我们希望对于一个点,只遍历到他一次就可以统计上所有贡献。

可能对一个点产生影响的路径,其s和t都应在以该点为根的子树中,且lca深度小于等于该点。那么我们如果递归来统计贡献,在到节点i时,先记录一下至此之前的结果(s和t不在子树i内),遍历完子树i后再用现结果减去它,就能得到i的答案。

需要统计两个量,分别代表前后半段,bkt1[dep[i]+w[i]]是走前半段路时被i观测到的贡献,bkt2[w[i]-dep[i]+n](防溢出)即是后半段的贡献。注意此处数组大小都是2*n。

如何计算点i产生的贡献?类似树上差分的思路。如果i是某个路径的s,++bkt1[dep[s]];如果i是某个路径的t,++bkt2[dep[s]-2*dep[lca]];如果是某个lca,--bkt1[dep[s]];如果是某个lca的父节点,--bkt2[dep[s]-2*dep[lca]]。保证路径上每个点的贡献都被加上且仅加了一遍。遍历到某个i如何知道它是不是特殊点呢?这个是要预处理好的,对于每个路径,分别记录s t lca lca的父节点和他们产生的贡献,然后全部按dfs序排好,在递归的时候就很容易计算了。

思维量比较大,代码实现倒似乎蛮简单的。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=300010;
struct edge{
	int y,next;
}data[N<<1];
struct route{
	int u,tp,x;
}q1[N<<2];
int n,m,num,num1,num2,num3,h[N],dep[N],fa[N][20],w[N],rnk[N],bkt1[N<<1],bkt2[N<<1],ans[N]; 
inline int read(){
	int x=0;char ch=getchar();
	while(ch<'0'||ch>'9')ch=getchar();
	while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
	return x;
}
inline void addedge(int u,int v){
	data[++num].y=v,data[num].next=h[u],h[u]=num;
	data[++num].y=u,data[num].next=h[v],h[v]=num;
}
void dfs1(int u,int d){
	dep[u]=d,rnk[u]=++num1;
	for(int i=h[u],v;i!=-1;i=data[i].next){
		v=data[i].y;
		if(v!=fa[u][0]){fa[v][0]=u;dfs1(v,d+1);}
	}
}
inline bool cmp1(route a,route b){
	return rnk[a.u]<rnk[b.u];
}
inline int lca(int s,int t){
	if(dep[s]<dep[t])swap(s,t);
	for(int i=19;i>=0;--i)if(dep[fa[s][i]]>=dep[t])s=fa[s][i];
	if(s==t)return s;
	for(int i=19;i>=0;--i)if(fa[s][i]!=fa[t][i])s=fa[s][i],t=fa[t][i];
	return fa[s][0];
}
void dfs2(int u){
	int x1=bkt1[dep[u]+w[u]],x2=bkt2[n+w[u]-dep[u]];
	while(q1[num3+1].u==u){
		++num3;
		if(q1[num3].tp==1)++bkt1[q1[num3].x];
		else if(q1[num3].tp==2)++bkt2[q1[num3].x];
		else if(q1[num3].tp==3)--bkt1[q1[num3].x];
		else --bkt2[q1[num3].x];
	}
	for(int i=h[u],v;i!=-1;i=data[i].next){v=data[i].y;if(v!=fa[u][0])dfs2(v);}
	ans[u]=bkt1[dep[u]+w[u]]+bkt2[n+w[u]-dep[u]]-x1-x2;
}
int main(){
	n=read(),m=read();
	memset(h,-1,sizeof h);num=num1=0;
	for(int i=1,u,v;i<n;++i)u=read(),v=read(),addedge(u,v);
	fa[1][0]=0,dep[0]=-1;dfs1(1,0);
	for(int i=1;i<=19;++i)for(int j=1;j<=n;++j)fa[j][i]=fa[fa[j][i-1]][i-1];
	for(int i=1;i<=n;++i)w[i]=read();
	num2=0;
	for(int i=1,s,t,l;i<=m;++i){
		s=read(),t=read(),l=lca(s,t);
		q1[++num2].u=s,q1[num2].tp=1,q1[num2].x=dep[s];
		q1[++num2].u=t,q1[num2].tp=2,q1[num2].x=dep[s]+n-dep[l]*2;
		q1[++num2].u=l,q1[num2].tp=3,q1[num2].x=dep[s];
		if(fa[l][0]!=0)q1[++num2].u=fa[l][0],q1[num2].tp=4,q1[num2].x=dep[s]+n-dep[l]*2;
	}
	sort(q1+1,q1+num2+1,cmp1);
	num3=0;dfs2(1);
	for(int i=1;i<=n;++i)printf("%d ",ans[i]);
	return 0;
} 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值