title
BZOJ 4719
LUOGU 1600
简化题意:
小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\) 的观察员可以观察到这个玩家。
analysis
对于一个人,他的路程会分为两段,一段向上(根),一段向下,考虑在向上过程中他能产生贡献的观察者具有什么性质:设出发点深度为 \(dep[x]\),观察者深度为 \(dep[y]\),观察的时间为 \(t\),需满足 \(dep[x]−dep[y]=t\),换句话说就是 \(dep[y]+t=dep[x]\) 。向下那一段的推导类似,下面的部分也只以向上的路径为例来解释。
现在我们记录每个点下方出发点深度为 \(dep[x]\) 的人数,需要对于每个出发点更新出发点到出发点与目的地的 \(lca\) 的所有点,想到到开一颗权值线段树,用线段树合并不断把信息往上传,在 \(lca\) 处消除这次更新的影响,这样一来直接我们就可以在树上统计答案了。向下路径的处理与向上路径类似。
code
#include
const int maxn=3e5+10;
namespace IO
{
char buf[1<<15],*fs,*ft;
inline char getc() { return (ft==fs&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),ft==fs))?0:*fs++; }
templateinline void read(T &x)
{
x=0;
T f=1, ch=getchar();
while (!isdigit(ch) && ch^'-') ch=getchar();
if (ch=='-') f=-1, ch=getchar();
while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
x*=f;
}
char Out[1<<24],*fe=Out;
inline void flush() { fwrite(Out,1,fe-Out,stdout); fe=Out; }
templateinline void write(T x,char str)
{
if (!x) *fe++=48;
if (x<0) *fe++='-', x=-x;
T num=0, ch[20];
while (x) ch[++num]=x%10+48, x/=10;
while (num) *fe++=ch[num--];
*fe++=str;
}
}
using IO::read;
using IO::write;
int ver[maxn<<1],Next[maxn<<1],head[maxn],len;
inline void add(int x,int y)
{
ver[++len]=y,Next[len]=head[x],head[x]=len;
}
namespace SGT
{
struct Orz{int l,r,z;}c[maxn*60];
int num=0;
inline void Change(int &x,int l,int r,int k,int z)
{
if (!x) x=++num;
c[x].z+=z;
if (l==r) return ;
int mid=(l+r)>>1;
if (k<=mid) Change(c[x].l,l,mid,k,z);
else Change(c[x].r,mid+1,r,k,z);
}
inline int query(int x,int l,int r,int k)
{
if (l==r) return c[x].z;
int mid=(l+r)>>1;
if (k<=mid) return query(c[x].l,l,mid,k);
else return query(c[x].r,mid+1,r,k);
}
inline int merge(int x,int y)
{
if (!x) return y;
if (!y) return x;
c[x].l=merge(c[x].l,c[y].l);
c[x].r=merge(c[x].r,c[y].r);
c[x].z=c[x].z+c[y].z;
return x;
}
}
using SGT::Change;
using SGT::query;
using SGT::merge;
namespace lca
{
int dfn[maxn],id;
int f[maxn][21],dep[maxn];
inline void dfs(int x)
{
dfn[x]=++id;
for (int i=1; i<=20; ++i) f[x][i]=f[f[x][i-1]][i-1];
for (int i=head[x]; i; i=Next[i])
{
int y=ver[i];
if (y==f[x][0]) continue;
f[y][0]=x;
dep[y]=dep[x]+1;
dfs(y);
}
}
inline int LCA(int x,int y)
{
if (dep[x]>dep[y]) std::swap(x,y);
for (int i=20; i>=0; --i)
if (dep[y]-(1<=dep[x]) y=f[y][i];
if (x==y) return x;
for (int i=20; i>=0; --i)
if (f[x][i]^f[y][i]) x=f[x][i],y=f[y][i];
return f[x][0];
}
}
using lca::dfs;
using lca::LCA;
using lca::dep;
using lca::f;
int tot,n,m,u[maxn],v[maxn];
int ans[maxn],w[maxn];
inline void get(int x)
{
for (int i=head[x]; i; i=Next[i])
{
int y=ver[i];
if (y==f[x][0]) continue;
get(y);
u[x]=merge(u[x],u[y]), v[x]=merge(v[x],v[y]);
}
ans[x]=query(u[x],1,tot,dep[x]+w[x])+query(v[x],1,tot,w[x]-dep[x]+n);
}
int main()
{
read(n);read(m); tot=n<<1;
for (int i=1,x,y; i