问题 G: 天天爱跑步
时间限制: 2 Sec 内存限制: 512 MB题目描述
小C同学认为跑步非常有趣,于是决定制作一款叫做《天天爱跑步》的游戏。《天天爱跑步》是一个养成类游戏,需要玩家每天按时上线,完成打卡任务。
这个游戏的地图可以看作一棵包含n个结点和n-1条边的树,每条边连接两个结点,且任意两个结点存在一条路径互相可达。树上结点编号为从1到n的连续正整数。
现在有m个玩家,第i个玩家的起点为Si,终点为Ti。每天打卡任务开始时,所有玩家在第0秒同时从自己的起点出发,以每秒跑一条边的速度,不间断地沿着最短路径向着自己的终点跑去,跑到终点后该玩家就算完成了打卡任务。(由于地图是一棵树,所以每个人的路径是唯一的)
小C想知道游戏的活跃度, 所以在每个结点上都放置了一个观察员。 在结点的观察员会选择在第Wj秒观察玩家, 一个玩家能被这个观察员观察到当且仅当该玩家在第Wj秒也理到达了结点J 。 小C想知道每个观察员会观察到多少人?
注意: 我们认为一个玩家到达自己的终点后该玩家就会结束游戏, 他不能等待一 段时间后再被观察员观察到。 即对于把结点J作为终点的玩家: 若他在第Wj秒重到达终点,则在结点J的观察员不能观察到该玩家;若他正好在第Wj秒到达终点,则在结点的观察员可以观察到这个玩家。
输入
第一行有两个整数n和m。其中n代表树的结点数量,同时也是观察员的数量,m代表玩家的数量。
接下来n-1行每行两个整数u和v,表示结点u到结点v有一条边。
接下来一行n个整数,其中第j个整数为Wj,表示结点j出现观察员的时间。
接下来m行,每行两个整数Si和Ti,表示一个玩家的起点和终点。
对于所有的数据,保证1≤Si,Ti≤n,0≤ Wj ≤n。
输出
样例输入
6 3
2 3
1 2
1 4
4 5
4 6
0 2 5 1 2 3
1 5
1 3
2 6
样例输出
2 0 0 1 1 1
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
struct tree{
int u,v,next;
}l[601000];
struct tree2{
int st,en,go;
}ll[301000];
int n,lian[301000],e,cnt,m,dep[301000],ldfn[301000],rdfn[301000];
int lc[7501000],rc[7501000],w[301000],p[300100][22],root[7501000];
int son[301000],size[301000],hh[7501000],fa[301000],an[300100],num;
void bian(int,int);
void dfs(int);
void work();
int lca(int,int);
void change(int,int,int,int,int&);
int search(int,int,int,int,int);
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
bian(x,y);
bian(y,x);
}
for(int i=1;i<=n;i++)
scanf("%d",&w[i]);
dfs(1);
work();
for(int i=1;i<=m;i++)
{
scanf("%d%d",&ll[i].st,&ll[i].en);
ll[i].go=lca(ll[i].st,ll[i].en);
}
for(int i=1;i<=m;i++)
{
change(ldfn[ll[i].st],1,1,n,root[dep[ll[i].st]]);
change(ldfn[fa[ll[i].go]],-1,1,n,root[dep[ll[i].st]]);
}
for(int i=1;i<=n;i++)
an[i]+=search(ldfn[i],rdfn[i],1,n,root[dep[i]+w[i]]);
memset(lc,0,sizeof(lc));
memset(rc,0,sizeof(rc));
memset(root,0,sizeof(root));
cnt=0;
memset(hh,0,sizeof(hh));
for(int i=1;i<=m;i++)
{
change(ldfn[ll[i].en],1,1,n,root[dep[ll[i].st]-2*dep[ll[i].go]+2*n]);
change(ldfn[ll[i].go],-1,1,n,root[dep[ll[i].st]-2*dep[ll[i].go]+2*n]);
}
for(int i=1;i<=n;i++)
an[i]+=search(ldfn[i],rdfn[i],1,n,root[w[i]-dep[i]+2*n]);
for(int i=1;i<=n;i++)
printf("%d ",an[i]);
return 0;
}
void bian(int x,int y)
{
e++;
l[e].u=x;
l[e].v=y;
l[e].next=lian[x];
lian[x]=e;
}
void dfs(int x)
{
ldfn[x]=++num;
size[x]=1;
for(int i=lian[x];i;i=l[i].next)
{
int v=l[i].v;
if(v!=fa[x])
{
fa[v]=x;
dep[v]=dep[x]+1;
dfs(v);
size[x]+=size[v];
if(size[v]>size[son[x]])
son[x]=v;
}
}
rdfn[x]=num;
}
void work()
{
for(int i=1;i<=n;i++)
p[i][0]=fa[i];
for(int i=1;i<=20;i++)
for(int j=1;j<=n;j++)
if(p[j][i-1]!=0)
p[j][i]=p[p[j][i-1]][i-1];
}
int lca(int x,int y)
{
if(dep[x]<dep[y])
swap(x,y);
int k=dep[x]-dep[y];
for(int i=20;i>=0;i--)
if(k-(1<<i)>=0)
{
k-=(1<<i);
x=p[x][i];
}
if(x==y) return x;
for(int i=20;i>=0;i--)
if(p[x][i]!=0&&p[x][i]!=p[y][i])
{
x=p[x][i];
y=p[y][i];
}
return fa[x];
}
void change(int x,int num,int le,int ri,int &now)
{
if(x==0) return;
if(now==0)
now=++cnt;
hh[now]+=num;
if(le==ri) return;
int mid=(le+ri)>>1;
if(x<=mid)
change(x,num,le,mid,lc[now]);
else
change(x,num,mid+1,ri,rc[now]);
}
int search(int ll,int rr,int le,int ri,int now)
{
if(now==0)
return 0;
if(ll==le&&rr==ri)
return hh[now];
int mid=(le+ri)>>1;
if(rr<=mid)
return search(ll,rr,le,mid,lc[now]);
else
if(ll>mid)
return search(ll,rr,mid+1,ri,rc[now]);
else
return search(ll,mid,le,mid,lc[now])+search(mid+1,rr,mid+1,ri,rc[now]);
}
动态开点线段树可以有很多打法,不一定像我一样打。