2018.11.09【NOIP2016】【洛谷P1600】天天爱跑步(树上差分)

传送门


解析:

据说这是NOIP历年最难一道题。。但是真的没有宝藏难啊我觉得。。。

思路:

答案分两类统计,一种是子树中过来,一种是其他地方过来。那么路径就被拆分成两部分了,一部分是 S − > l c a S->lca S>lca,一部分是 l c a − > T lca->T lca>T,两部分分别对应两种答案的统计,这就可以树上差分了。

第一种:从子树中经过。设统计答案的点为 u u u
子树中为起点则必须要满足 d e p u + w u = d e p S dep_u+w_u=dep_S depu+wu=depS,即统计子树内有多少个深度为 d e p u + w u dep_u+w_u depu+wu的点,这个就是树上差分的基础应用了。

第二种:从其他子树来。设统计答案的点为 u u u
则需要记录入答案的路径需要满足 L C A ( u , S ) = = L C A ( S , T ) LCA(u,S)==LCA(S,T) LCA(u,S)==LCA(S,T),不过这个不需要体现在代码上。而满足路径长度则需要 w u − d e p u = d e p l c a × 2 − d e p S w_u-dep_u=dep_{lca}\times 2-dep_S wudepu=deplca×2depS
这个等式左边只和 u u u有关,右边和 u u u没有关系,同样树上差分解决。


代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define re register
#define gc getchar
#define pc putchar
#define cs const

inline int getint(){
	re int num;
	re char c;
	while(!isdigit(c=gc()));num=c^48;
	while(isdigit(c=gc()))num=(num<<1)+(num<<3)+(c^48);
	return num;
}

cs int N=300005;
int bin1[N<<1],bbb[N<<1];
int *cs bin2=bbb+N;
int last[N],nxt[N<<1],to[N<<1],ecnt;
inline void addedge(int u,int v){
	nxt[++ecnt]=last[u],last[u]=ecnt,to[ecnt]=v;
	nxt[++ecnt]=last[v],last[v]=ecnt,to[ecnt]=u;
}

int w[N];
int fa[N],top[N],dep[N],siz[N],son[N];
inline void dfs1(int u){
	siz[u]=1;
	for(int re e=last[u],v=to[e];e;v=to[e=nxt[e]]){
		if(v==fa[u])continue;
		fa[v]=u;
		dep[v]=dep[u]+1;
		dfs1(v);
		siz[u]+=siz[v];
		if(siz[v]>siz[son[u]])son[u]=v;
	}
}

inline void dfs2(int u){
	for(int re e=last[u],v=to[e];e;v=to[e=nxt[e]]){
		if(v==fa[u])continue;
		if(son[u]==v)top[v]=top[u];
		else top[v]=v;
		dfs2(v);
	}
}

inline void tree_dissection(int root=1){
	dfs1(root);
	top[root]=root;
	dfs2(root);
}

inline int LCA(int u,int v){
	while(top[u]^top[v]){
		if(dep[top[u]]>dep[top[v]])swap(u,v);
		v=fa[top[v]];
	}
	return dep[u]>dep[v]?v:u;
}

vector<int> pos1[N],pos2[N],add1[N],add2[N];
int ans[N];
inline void dfs(int u){
	int pre1=bin1[dep[u]+w[u]],pre2=bin2[w[u]-dep[u]];
	for(int re e=last[u],v=to[e];e;v=to[e=nxt[e]]){
		if(v==fa[u])continue;
		dfs(v);
	}
	for(int re i=0;i<pos1[u].size();++i)bin1[pos1[u][i]]+=add1[u][i];
	for(int re i=0;i<pos2[u].size();++i)bin2[pos2[u][i]]+=add2[u][i];
	ans[u]+=bin1[dep[u]+w[u]]-pre1+bin2[w[u]-dep[u]]-pre2;
}

int n,m;
signed main(){
	n=getint();
	m=getint();
	for(int re i=1;i<n;++i){
		int u=getint(),v=getint();
		addedge(u,v);
	}
	tree_dissection();
	for(int re i=1;i<=n;++i)
	w[i]=getint();
	for(int re i=1;i<=m;++i){
		int u=getint(),v=getint(),lca=LCA(u,v);
		pos1[u].push_back(dep[u]);add1[u].push_back(1);
		pos1[lca].push_back(dep[u]);add1[lca].push_back(-1);
		pos2[v].push_back(dep[u]-2*dep[lca]);add2[v].push_back(1);
		pos2[fa[lca]].push_back(dep[u]-2*dep[lca]);add2[fa[lca]].push_back(-1);
	}
	dfs(1);
	for(int re 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、付费专栏及课程。

余额充值