Luogu P1600 [NOIP2016 提高组] 天天爱跑步

题链

[P1600 NOIP2016 提高组] 天天爱跑步 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

分析

肯定需要枚举的是观察员

感觉像换根DP,所以想换根DP一样做,分成从上到下和从下到上两部分

设路径是\((s,l,t)\)

  • 从下往上(s,l),设观察员为u

    \[d[s]=w[u]+d[u] \]

  • 从上往下(l,t)

    \[Len[s,t]-d[t]+n=w[u]-d[u]+n \]

所以可以树上差分后线段树合并\(O(nlgn)\)

但是有\(O(n)\)的做法:

  • 首先求LCA用tarjan
  • 然后变成了在某节点对某权值进行变化,然后求子树中这些操作后某权值的大小
    • 这可以利用差分

求这颗子树的所有权值的变化相当于蓝色区域减去红色区域

所以这个点的答案是DFS出这个点时的权值减去DFS到这个点时的权值,妙啊

#include<bits/stdc++.h>
using namespace std;

const int N=3e5+5;
int n,m,lg[N],f[N][20],d[N],ans[N],w[N],t1[N<<1],t2[N<<1];
vector<int>V[N];
struct A{int v,w; };
vector<A>V1[N],V2[N];


void dfs(int fa,int u) {
	d[u]=!fa?0:d[fa]+1;
	f[u][0]=fa;
	for(int i=1;i<=lg[d[u]];i++) {
		f[u][i]=f[f[u][i-1]][i-1];
	}
	for(int v:V[u]) {
		if(v!=fa) dfs(u,v);
	}
}
inline int lca(int u,int v) {
	if(d[u]>d[v]) swap(u,v);
	while(d[u]<d[v]) v=f[v][lg[d[v]-d[u]]];
	if(u==v) return u;
	for(int i=lg[d[u]];i>=0;i--) {
		if(f[u][i]!=f[v][i]) {
			u=f[u][i],v=f[v][i];
		}
	}
	return f[u][0];
}
void dgs(int fa,int u) {
	if(w[u]+d[u]<=n) ans[u]-=t1[w[u]+d[u]];
	ans[u]-=t2[w[u]-d[u]+n];
	for(A v:V1[u]) {
		t1[v.v]+=v.w;
	}
	for(A v:V2[u]) {
		t2[v.v+n]+=v.w;
	}
	for(int v:V[u]) {
		if(v!=fa) {
			dgs(u,v);
		}
	}
	if(w[u]+d[u]<=n) ans[u]+=t1[w[u]+d[u]];
	ans[u]+=t2[w[u]-d[u]+n];
}
int main() {
	scanf("%d%d",&n,&m);
	for(int i=1;i<n;i++) {
		int u,v; scanf("%d%d",&u,&v);
		V[u].push_back(v),V[v].push_back(u);
	}
	lg[0]=-1;
	for(int i=1;i<=n;i++) lg[i]=lg[i>>1]+1;
	dfs(0,1);
	for(int i=1;i<=n;i++) scanf("%d",&w[i]);
	for(int i=1;i<=m;i++) {
		int u,v; scanf("%d%d",&u,&v);
		int l=lca(u,v),len=d[u]+d[v]-d[l]-d[l];
		V1[u].push_back((A){d[u],1});
		V1[f[l][0]].push_back((A){d[u],-1});
		if(v!=l) {
			V2[v].push_back((A){len-d[v],1});
			V2[l].push_back((A){len-d[v],-1});
		}
	}
	dgs(0,1);
	for(int i=1;i<=n;i++) {
		printf("%d%c",ans[i],i==n?'\n':' ');
	}
	return 0;
}
posted @ 2021-04-28 16:26  wwwsfff  阅读(52)  评论(0)    收藏  举报