NOIp2016 D1T2 天天爱跑步

传送门

解决树上问题的一个技巧:简单路径拆分成链,再分别考虑
令某个人的路径为 P P P,其起点为 s s s,终点为 t t t l c a ( s , t ) = g lca(s,t)=g lca(s,t)=g
一条路径拆分为向上和向下走,即:
从起点到 g g g的下面一个点(令其为 k k k,即 f a [ k ] = g fa[k]=g fa[k]=g,这是避免重复),和从 g g g到终点两个部分。

先考虑向上走的部分:
一个人从 s s s出发到 k k k,如果他能被某一个观察点 u u u看见,首先要满足这条路径经过了 u u u
其次,要满足:
d e p [ s ] − d e p [ u ] = w [ u ] ⇒ d e p [ s ] = d e p [ u ] + w [ u ] dep[s]-dep[u]=w[u] \Rightarrow dep[s]=dep[u]+w[u] dep[s]dep[u]=w[u]dep[s]=dep[u]+w[u]
我们发现对于一个确定的点 u u u d e p [ u ] + w [ u ] dep[u]+w[u] dep[u]+w[u]是一个定值。
于是我们就要找到 u u u的子树中有多少个点 i i i作为起点,它的深度满足 d e p [ i ] = d e p [ u ] + w [ u ] dep[i]=dep[u]+w[u] dep[i]=dep[u]+w[u],并且这条路径经过了 u u u

对于前面的条件,可以用桶来记录。简单地说,就是每个点有 n n n个桶,记录其子树中深度为 1 1 1~ n n n的点分别有多少个,而一个点 u u u能看到的上升路径上的人就是 c n t [ d e p [ u ] + w [ u ] ] cnt[dep[u]+w[u]] cnt[dep[u]+w[u]]

而由于有后面的限制,我们考虑换用差分来记录。对于经过型问题,差分是一个常用套路。即:在 s s s上打一个 c n t [ d e p [ s ] ] cnt[dep[s]] cnt[dep[s]]加一的标记,而在 f a [ k ] fa[k] fa[k]上打一个 c n t [ d e p [ s ] ] cnt[dep[s]] cnt[dep[s]]减一的标记。统计 u u u的子树和时,如果一条路径没有经过 u u u,它就不会有贡献,否则如果这条路径的起点在 u u u的子树内且这条路径经过了 u u u,它的贡献就是 1 1 1
这样的话,对于一个 u u u,就只需要求出它的子树中 c n t [ d e p [ u ] + w [ u ] ] cnt[dep[u]+w[u]] cnt[dep[u]+w[u]]的和即可。

那么向下走的路径也是类似的:
g g g出发到 t t t,如果能被 u u u看见,也首先要满足该路径经过了 u u u
其次,有:
( d e p [ s ] − d e p [ g ] ) + ( d e p [ u ] − d e p [ g ] ) = w [ u ] ⇒ d e p [ s ] − 2 ∗ d e p [ g ] = w [ u ] − d e p [ u ] (dep[s]-dep[g])+(dep[u]-dep[g])=w[u] \Rightarrow dep[s]-2*dep[g]=w[u]-dep[u] (dep[s]dep[g])+(dep[u]dep[g])=w[u]dep[s]2dep[g]=w[u]dep[u]

相当于是把上面一种情况的一个点的标记值从 d e p [ s ] dep[s] dep[s]变为了 d e p [ s ] − 2 ∗ d e p [ g ] dep[s]-2*dep[g] dep[s]2dep[g],而所要统计子树和的值从 w [ u ] + d e p [ u ] w[u]+dep[u] w[u]+dep[u]变为了 w [ u ] − d e p [ u ] w[u]-dep[u] w[u]dep[u]

但是发现,如果这样每个点用 n n n个桶来记录,空间是装不下的。下面的处理方法比较巧妙,其实也就是子树和的一个常用转化:一个点的子树对应着 d f s dfs dfs序连续的一段区间,那么我们可以用 d f s dfs dfs完这个子树的总贡献减去 d f s dfs dfs该点前的总贡献 来得到这个子树的贡献。具体做法如下:

每个点用四个 v e c t o r vector vector记录。分别是第一种情况的加、减标记,以及第二种情况的加、减标记。下面假设要求出点 u u u c n t [ v a l ] cnt[val] cnt[val]的子树和:
我们记录一个全局的桶 s u m [ 1 sum[1 sum[1~ n ] n] n]。在搜索到每个点时,根据该点的所有标记值(加或减),修改 s u m sum sum的值。在 d f s dfs dfs每个点的儿子之前,先记录一下开始搜索之前的 s u m [ v a l ] sum[val] sum[val]的值,令其为 S U M SUM SUM,然后搜索完若干儿子之后,再加上 u u u的贡献,会有一个新的 s u m [ v a l ] sum[val] sum[val]的值,令其为 S U M ′ SUM' SUM。于是该点的子树中 c n t [ v a l ] cnt[val] cnt[val]的和就是 S U M ′ − S U M SUM'-SUM SUMSUM
具体代码实现还是比较好理解的。要注意的是第二种情况有减法,可能小于 0 0 0,给所有值加上一个 n n n就可以了。

#include<bits/stdc++.h>
using namespace std;
const int maxn=3e5+10,Log=19;
int Head[maxn],V[maxn<<1],Next[maxn<<1],cnt=0;
int n,m,u,v,g,f[maxn][Log],dep[maxn],w[maxn],sum1[maxn],sum2[maxn<<1],ans[maxn];
vector<int> add1[maxn],dec1[maxn],add2[maxn],dec2[maxn];
inline void addedge(int u,int v){Next[++cnt]=Head[u],V[cnt]=v,Head[u]=cnt;}
inline int read(){
	int x=0;char ch=getchar();
	while(!isdigit(ch)) ch=getchar();
	while(isdigit(ch)) x=x*10+ch-'0',ch=getchar();
	return x;
}
inline void dfs1(int u,int fa){
	f[u][0]=fa,dep[u]=dep[fa]+1;
	for(int i=1;i<Log;++i) f[u][i]=f[f[u][i-1]][i-1];
	for(int i=Head[u];i;i=Next[i]) if(V[i]!=fa)
		dfs1(V[i],u);
}
inline int lca(int u,int v){
	if(dep[u]<dep[v]) swap(u,v);
	for(int i=Log-1;i>=0;--i) if(dep[f[u][i]]>=dep[v]) u=f[u][i];
	if(u==v) return u;
	for(int i=Log-1;i>=0;--i) if(f[u][i]!=f[v][i]) u=f[u][i],v=f[v][i];
	return f[u][0];
}
inline void dfs2(int u,int f){
	int d1=w[u]+dep[u],d2=w[u]-dep[u]+maxn,L1=sum1[d1],L2=sum2[d2];
	for(int i=Head[u];i;i=Next[i]) if(V[i]!=f)
		dfs2(V[i],u);
	for(int i=0;i<add1[u].size();++i) ++sum1[add1[u][i]];
	for(int i=0;i<dec1[u].size();++i) --sum1[dec1[u][i]];
	ans[u]+=sum1[d1]-L1;
	for(int i=0;i<add2[u].size();++i) ++sum2[add2[u][i]];
	for(int i=0;i<dec2[u].size();++i) --sum2[dec2[u][i]];
	ans[u]+=sum2[d2]-L2;
}
inline void print(int x){
	if(x>9) print(x/10);
	putchar(x%10+'0');
}
int main(){
	//freopen("1578.in","r",stdin);
	n=read(),m=read();
	for(int i=1;i< n;++i)
		u=read(),v=read(),addedge(u,v),addedge(v,u);
	dfs1(1,0);for(int i=1;i<=n;++i) w[i]=read();
	while(m--){
		u=read(),v=read(),g=lca(u,v);
		add1[u].push_back(dep[u]);
		dec1[g].push_back(dep[u]);
		add2[v].push_back(dep[u]-2*dep[g]+maxn);
		dec2[f[g][0]].push_back(dep[u]-2*dep[g]+maxn);
	}dfs2(1,0);
	for(int i=1;i<=n;++i) print(ans[i]),putchar(' ');
	return puts(""),0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值