bzoj4719: [Noip2016]天天爱跑步 树上差分
Posted nan-cheng
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了bzoj4719: [Noip2016]天天爱跑步 树上差分相关的知识,希望对你有一定的参考价值。
Description
小c同学认为跑步非常有趣,于是决定制作一款叫做《天天爱跑步》的游戏。?天天爱跑步?是一个养成类游戏,需要
玩家每天按时上线,完成打卡任务。这个游戏的地图可以看作一一棵包含 N个结点和N-1 条边的树, 每条边连接两
个结点,且任意两个结点存在一条路径互相可达。树上结点编号为从1到N的连续正整数。现在有个玩家,第个玩家的
起点为Si ,终点为Ti 。每天打卡任务开始时,所有玩家在第0秒同时从自己的起点出发, 以每秒跑一条边的速度,
不间断地沿着最短路径向着自己的终点跑去, 跑到终点后该玩家就算完成了打卡任务。 (由于地图是一棵树, 所以
每个人的路径是唯一的)小C想知道游戏的活跃度, 所以在每个结点上都放置了一个观察员。 在结点的观察员会选
择在第Wj秒观察玩家, 一个玩家能被这个观察员观察到当且仅当该玩家在第Wj秒也理到达了结点J 。 小C想知道
每个观察员会观察到多少人?注意: 我们认为一个玩家到达自己的终点后该玩家就会结束游戏, 他不能等待一 段时
间后再被观察员观察到。 即对于把结点J作为终点的玩家: 若他在第Wj秒重到达终点,则在结点J的观察员不能观察
到该玩家;若他正好在第Wj秒到达终点,则在结点的观察员可以观察到这个玩家。
Input
第一行有两个整数N和M 。其中N代表树的结点数量, 同时也是观察员的数量, M代表玩家的数量。
接下来n-1 行每行两个整数U和V ,表示结点U 到结点V 有一条边。
接下来一行N 个整数,其中第个整数为Wj , 表示结点出现观察员的时间。
接下来 M行,每行两个整数Si和Ti,表示一个玩家的起点和终点。
对于所有的数据,保证 。
1<=Si,Ti<=N,0<=Wj<=N
Output
输出1行N 个整数,第个整数表示结点的观察员可以观察到多少人。
首先可以得到两个式子
(dep[u]-dep[x]=w[x])
(dep[u]-2*dep[lca]+dep[x]=w[x])
换位得到
(w[x]+dep[x]=dep[u])
(w[x]-dep[x]=dep[u]-2*dep[lca])
(x)的值确定的时候,左边的值是确定的,因此我们考虑用x的子树更新(x)的答案。
用两个桶记录值,每(dfs)到一个点更新桶的值的个数。
用(dep[u])和(dep[u]-2*dep[lca])更新桶的情况,
用(w[x]+dep[x])和(w[x]-dep[x])查询(dfs)到(x)点时的答案。
注意(w[x]-dep[x])的答案可能为负,所以要加上(maxn)
回溯时更新当前节点的答案,答案更新时减去父亲节点的答案
对于(lca)等于起点和终点的特殊考虑。
另外,对于分裂成两条链(lca)可能会被统计两遍,最后特殊判断一下,如果被统计了两遍就减去一遍,
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<vector>
const int maxn=3e5+10;
const int k=448;
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Dec(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
inline int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch==‘-‘)f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-‘0‘;ch=getchar();}
return x*f;
}
int n,m;
int fa[maxn],w[maxn],s[maxn],t[maxn];
vector<int>mp[maxn];
int son[maxn],size[maxn],top[maxn],dep[maxn];
struct que{int v,tag;};
vector<que>w1[maxn];
vector<que>w2[maxn];
int ans[maxn],lca[maxn];
void dfs1(int x,int f,int d){
fa[x]=f;
dep[x]=d;
size[x]=1;
for(int i=0;i<(int)mp[x].size();i++){
int u=mp[x][i];
if(u==f)continue;
dfs1(u,x,d+1);
size[x]+=size[u];
if(!son[x]||size[son[x]]<size[u])son[x]=u;
}
}
void dfs2(int x,int t){
top[x]=t;
if(!son[x])return ;
dfs2(son[x],t);
for(int i=0;i<(int)mp[x].size();i++){
int u=mp[x][i];
if(u==son[x]||u==fa[x])continue;
dfs2(u,u);
}
}
int LCA(int x,int y){
int fx=top[x],fy=top[y];
while(fx!=fy){
if(dep[fx]>dep[fy])x=fa[fx];
else y=fa[fy];
fx=top[x];fy=top[y];
}
if(dep[x]>dep[y])return y;
else return x;
}
int bac1[2*maxn],bac2[2*maxn];
void dfs(int x,int a,int b){
for(int i=0;i<(int)w1[x].size();i++)
// For(i,0,(int)w1[x].size()-1) //不强制转换会RE
bac1[w1[x][i].v+maxn]+=w1[x][i].tag;//更新桶内情况
for(int i=0;i<(int)w2[x].size();i++)
// For(i,0,(int)w2[x].size()-1)
bac2[w2[x][i].v+maxn]+=w2[x][i].tag;//更新桶内情况
for(int i=0;i<(int)mp[x].size();i++){
int u=mp[x][i];
if(u==fa[x])continue;
dfs(u,bac1[dep[u]+w[u]+maxn],bac2[w[u]-dep[u]+maxn]);
}
ans[x]+=bac1[dep[x]+w[x]+maxn]+bac2[w[x]-dep[x]+maxn]-a-b;//a,b是父亲节点的答案,要减去父亲节点的情况就是当前节点的结果
}
int main()
{
n=read();m=read();
For(i,1,n-1){
int u=read(),v=read();
mp[u].push_back(v);
mp[v].push_back(u);
}
For(i,1,n)w[i]=read();
For(i,1,m)s[i]=read(),t[i]=read();
dfs1(1,0,0);
dfs2(1,1);
For(i,1,m)lca[i]=LCA(s[i],t[i]);
For(i,1,m){
if(lca[i]==t[i]){
w1[s[i]].push_back((que){dep[s[i]],1});
w1[fa[t[i]]].push_back((que){dep[s[i]],-1});
}
else if(lca[i]==s[i]){
w2[t[i]].push_back((que){dep[s[i]]-2*dep[lca[i]],1});
w2[fa[s[i]]].push_back((que){dep[s[i]]-2*dep[lca[i]],-1});
}
else{
if(w[lca[i]]+dep[lca[i]]==dep[s[i]])--ans[lca[i]];
w1[s[i]].push_back((que){dep[s[i]],1});
w1[fa[lca[i]]].push_back((que){dep[s[i]],-1});
w2[t[i]].push_back((que){dep[s[i]]-2*dep[lca[i]],1});
w2[fa[lca[i]]].push_back((que){dep[s[i]]-2*dep[lca[i]],-1});
}
}
dfs(1,0,0);
For(i,1,n)printf("%d ",max(0,ans[i]));
return 0;
}
以上是关于bzoj4719: [Noip2016]天天爱跑步 树上差分的主要内容,如果未能解决你的问题,请参考以下文章
bzoj4719: [Noip2016]天天爱跑步 树上差分