P1600 [NOIP 2016 提高组] 天天爱跑步

P1600 [NOIP 2016 提高组] 天天爱跑步

题目背景

NOIP2016 提高组 D1T2

题目描述

小 C 同学认为跑步非常有趣,于是决定制作一款叫做《天天爱跑步》的游戏。《天天爱跑步》是一个养成类游戏,需要玩家每天按时上线,完成打卡任务。

这个游戏的地图可以看作一棵包含 \(n\) 个结点和 \(n-1\) 条边的树,每条边连接两个结点,且任意两个结点存在一条路径互相可达。树上结点编号为从 \(1\)\(n\) 的连续正整数。

现在有 \(m\) 个玩家,第 \(i\) 个玩家的起点为 \(s_i\),终点为 \(t_i\)。每天打卡任务开始时,所有玩家在第 \(0\) 秒同时从自己的起点出发,以每秒跑一条边的速度,不间断地沿着最短路径向着自己的终点跑去,跑到终点后该玩家就算完成了打卡任务。(由于地图是一棵树,所以每个人的路径是唯一的)

小 C 想知道游戏的活跃度,所以在每个结点上都放置了一个观察员。在结点 \(j\) 的观察员会选择在第 \(w_j\) 秒观察玩家,一个玩家能被这个观察员观察到当且仅当该玩家在第 \(w_j\) 秒也正好到达了结点 \(j\)。小 C 想知道每个观察员会观察到多少人?

注意:我们认为一个玩家到达自己的终点后该玩家就会结束游戏,他不能等待一 段时间后再被观察员观察到。 即对于把结点 \(j\) 作为终点的玩家:若他在第 \(w_j\) 秒前到达终点,则在结点 \(j\) 的观察员不能观察到该玩家;若他正好在第 \(w_j\) 秒到达终点,则在结点 \(j\) 的观察员可以观察到这个玩家。

Solution:

偶然发现我居然没有写这题的结题报告?赶紧水一发。

首先对于这样的树上路径问题我们都可以把这条路径 (x,y) 拆成上行 (x,lca) 和下行 (lca,y) 两部分:假设一个人睁眼的时间是 \(w_i\) 那么对于上行链,应该满足:\(dep_x-dep_i=w_i\),然后对于下行链:\(dis_{(x,y)}-(dep_y-dep_i)=w_i\) 然后我们移项:

\[\begin{cases} w_i+dep_i=dep_x \\ w_i-dep_i=dis_{(x,y)}-dep_y \end{cases} \]

我们维护两个桶然后树上差分就好了。

但是还要注意的是,由于我们的桶是全局的,所有我们应该消除子树外的节点的答案对于当前节点的影响。我们在统计答案时应该先算一下统计到这个点时桶的状态 \(tmp\) 然后这个点真正的答案是将要离开这个点时的答案 \(ans\)\(tmp\) 作差。

然后这题就做完了。

Code:

#include<bits/stdc++.h>
using namespace std;
int n,m,cnt,cnta,cnte;
const int N=3e5+5;
const int lg=20;
int head[N],deep[N],fa[N][lg+5],heada[N],heade[N],s[N],e[N],num[N],w[N],dis[N];
struct Edge{
	int to,ne;
}edge[N<<1];
struct Point{
	int id,ne;
}lcap[N<<1],endp[N<<1];
void add(int x,int y)
{
	edge[++cnt].to=y;edge[cnt].ne=head[x];head[x]=cnt;
}
void dfs1(int u,int f)
{
	deep[u]=deep[f]+1;
	fa[u][0]=f;
	for(int i=1;i<=lg;i++)
	{
		fa[u][i]=fa[fa[u][i-1]][i-1];
	}
	for(int i=head[u];~i;i=edge[i].ne)
	{
		int v=edge[i].to;
		if(v==f)continue;
		dfs1(v,u);
	}
	return ;
}
int lca(int x,int y)
{
	if(x==y)return x;
	if(deep[x]<deep[y])swap(x,y);
	for(int i=lg;i>=0;i--)
	{
		if(deep[fa[x][i]]>=deep[y])x=fa[x][i];
	}
	if(x==y)return x;
	for(int i=lg;i>=0;i--)
	{
		if(fa[x][i]!=fa[y][i]){	x=fa[x][i];y=fa[y][i];}
	}
	return fa[x][0];
}
void adda(int id,int x)
{
	lcap[++cnta].id=id;lcap[cnta].ne=heada[x];heada[x]=cnta;
}
void adde(int id,int x)
{
	endp[++cnte].id=id;endp[cnte].ne=heade[x];heade[x]=cnte;
}
void init()
{
	for(int i=1;i<=n;i++)
	{
		head[i]=heada[i]=heade[i]=-1;
	}
}
int downline[N<<1],upline[N<<1],ans[N];
void dfs2(int u,int f)
{
	int dowm=downline[w[u]-deep[u]+N],up=upline[w[u]+deep[u]];//
	for(int i=head[u];~i;i=edge[i].ne)
	{
		int v=edge[i].to;
		if(v==f)continue;
		dfs2(v,u);
	}
	upline[deep[u]]+=num[u];//deep[lca]+w=deep[s];
	for(int i=heade[u];~i;i=endp[i].ne)
	{
		int id=endp[i].id;
		downline[dis[id]-deep[e[id]]+N]++;//dis=deep[e]-deep[lca]+w;
	}
	ans[u]+=downline[w[u]-deep[u]+N]-dowm+upline[w[u]+deep[u]]-up;
	for(int i=heada[u];~i;i=lcap[i].ne)
	{
		int id=lcap[i].id;
		downline[dis[id]-deep[e[id]]+N]--;
		upline[deep[s[id]]]--;
	}
	return ;
}
int main()
{
   // freopen("p1600.in","r",stdin);
  //  freopen("p1600.out","w",stdout);
	cin>>n>>m;
	init();
	for(int i=1,x,y;i<=n-1;i++)
	{
		scanf("%d%d",&x,&y);
		add(x,y);
		add(y,x);
	}
	dfs1(1,1);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&w[i]);
	}
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d",&s[i],&e[i]);
		int a=lca(s[i],e[i]);
		dis[i]=deep[s[i]]+deep[e[i]]-deep[a]*2;
		num[s[i]]++;
		adda(i,a);
		adde(i,e[i]);
		if(deep[a]+w[a]==deep[s[i]])ans[a]--;
	}
	dfs2(1,1);
	for(int i=1;i<=n;i++)
	{
		printf("%d ",ans[i]);
	}
	return 0;
}
posted @ 2025-02-07 18:40  liuboom  阅读(51)  评论(0)    收藏  举报