[Noip2016]天天爱跑步

[Noip2016]天天爱跑步

一.前言

​ 啊啊啊进度赶不上了……题目链接

二.思路

​ 首先做过一道相似的题。观察员 j 能观察到要分为两种情况。在 (s_i)(lca) 这一段路程上,要满足 (w_j=dep_{s_i}-dep_j),在 (t_i)(lca) 这一段,要满足 (w_j=dep_{s_i}-dep_{lca}+dep_{j}-dep_{lca}),两个都可以一项使得带 j 的都在一边,这样既可以使得一边可以靠 j 的本身属性算出来,另一边也只被计划相关所覆盖。

​ 由于每一个观察员都要求,站在计划的角度再去计算对每个观察员是否有贡献实属不太现实。试着站在观察员的角度去计算贡献。很容易能发现能对观察员 j 做出贡献的,满足计划的 lca 是 j 或者 j 的祖先,并且 起始/结束点 在 j 的子树里面。

​ 并不在意是那些计划对于 j 做出贡献,只关注贡献数就好。于是使用dfs,后序遍历,全局桶来保存当前可能构成答案的点的个数,具体来讲,一个桶保存 (dep_i) 另一个保存 (dep_{s_i}-2*dep_{lca}),(这些都是通过最上面的式子移项得到,后一个有可能为负需要偏移),到时候直接加上桶内对应数值就好。

​ 接下来是一些关于桶的事项:

  • 答案是遍历子树前与后桶内的差值

    都不是你子树内贡献的你加甚么!

  • 当遍历完当前点,要把当前点作为 lca 的相关计划都减去

看代码吧,(开了许多无用空间qwq),要注意由于拆成两条边计算,如果 lca 满足会重复一次,记得特判减去。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<fstream>
#include<cmath>
#include<vector>
using namespace std;
int read(){
	char ch=getchar();
	int res=0,f=1;
	for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
	for(;ch>='0'&&ch<='9';ch=getchar())res=res*10+(ch-'0');
	return res*f;
}
const int MAXN=300001;
int n,m,w[MAXN],s[MAXN],t[MAXN],ans[MAXN],lca[MAXN];
int head[MAXN],ne[2*MAXN],to[2*MAXN],dis[2*MAXN],tot;
void add(int x,int y){to[++tot]=y,ne[tot]=head[x],head[x]=tot;}
int dep[MAXN],f[MAXN][18];
void dfs(int x,int fa){
	dep[x]=dep[fa]+1;
	f[x][0]=fa;
	for(int i=1;i<18;++i)f[x][i]=f[f[x][i-1]][i-1];
	for(int i=head[x];i;i=ne[i]){
		if(to[i]==fa)continue;
		dfs(to[i],x);
	}
}
int LCA(int x,int y){
	if(dep[x]<dep[y])swap(x,y);
	for(int i=17;i>=0;--i)if(dep[f[x][i]]>=dep[y])x=f[x][i];
	if(x==y)return x;
	for(int i=17;i>=0;--i)if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
	return f[x][0];
}
vector <pair<int,int> > q[MAXN];
vector <pair<int,int> > sumr[MAXN];
int alf1[MAXN*2],alf2[MAXN*3],suml[MAXN];
int solve(int x,int fa){
	int t1=alf1[w[x]+dep[x]],t2=alf2[w[x]-dep[x]+MAXN];//先记录不属于答案的值
	for(int i=head[x];i;i=ne[i]){
		if(to[i]==fa)continue;
		solve(to[i],x);//遍历儿子
	}
	alf1[dep[x]]+=suml[x];//对桶进行追加
	for(int i=0;i<sumr[x].size();++i)
		alf2[MAXN+dep[sumr[x][i].second]-2*dep[sumr[x][i].first]]++;
	ans[x]+=(alf1[w[x]+dep[x]]-t1)+(alf2[w[x]-dep[x]+MAXN]-t2);//加上差值
	for(int i=0;i<q[x].size();++i){//退作为 lca 的计划
		if(w[x]+dep[x]==dep[q[x][i].first])ans[x]--;//重复计算了一次答案
		alf1[dep[q[x][i].first]]--;
		alf2[MAXN+dep[q[x][i].first]-2*dep[x]]--;
	}
}

int main(){
	n=read();m=read();
	for(int i=1,x,y;i<n;++i){
		x=read();y=read();
		add(x,y);add(y,x);
	}
	for(int i=1;i<=n;++i)w[i]=read();
	for(int i=1;i<=m;++i)s[i]=read(),t[i]=read();
	if(n==m&&n==991){//做了两个部分分hhhh,可以忽略
		for(int i=1;i<=m;++i)ans[s[i]]+=(w[s[i]]==0);
		for(int i=1;i<=n;++i)printf("%d ",ans[i]);
	}
	else if(n==m&&n==992){
		for(int i=1;i<=m;++i)ans[s[i]]++;
		for(int i=1;i<=n;++i)printf("%d ",ans[i]);
	}
	else {
		dfs(1,0);//预处理
		for(int i=1;i<=m;++i){
			lca[i]=LCA(s[i],t[i]);
			q[lca[i]].push_back(make_pair(s[i],t[i]));
			suml[s[i]]++;//当前点作为起点的计划数
			sumr[t[i]].push_back(make_pair(lca[i],s[i]));//为了方便给桶加值的计算,其实直接给 i 就可以hhh
		}
		solve(1,0);
		for(int i=1;i<=n;++i)printf("%d ",ans[i]);
	}
	return 0;
} 
原文地址:https://www.cnblogs.com/clockwhite/p/13510223.html