传送门
I Think
题意:给出一颗n个节点的树,每个节点i上有一个仅在时刻
wi
进行观察的观察员。有m个人在树上跑步,他们同时在0时刻开始从起点
Si
跑向
Ti
,每秒钟跑过1条边。观察员i观察到第j个人,当且仅当第j个人在第
wi
秒时恰好到达了i节点。每个人到达终点的下一秒即消失。问每个观察员能够观察到多少个人。
算法:线段树合并
思路:考虑一条路径
(S,T)
对节点u可能产生的贡献:
1)当S在u的子树内时,若
deep[S]−deep[u]=w[u]
时,该路径对点u有贡献1,移项后:
deep[u]+w[u]=deep[S]
2)当T在u的子树内时,若 Lca=LCA(S,T) , deep[S]−deep[Lca]+deep[u]−deep[Lca]=w[u] ,该路径对点u有贡献1,移项后:
deep[u]−w[u]=2×deep[Lca]−deep[S]
那么计算每个点的答案,则可以建立两颗权值线段树,在树上进行DFS时,自底向上分别在对应权值线段树中插入每个起/终点的贡献 deep[S] , 2×deep[Lca]−deep[S] 。一边向上走更新答案,一边合并子树的权值线段树。
3)当S,T均在u的子树内时,若 u=Lca 则路径S,T才可能产生一次贡献。但是根据操作1),2),此时该路径的贡献会被计算两次,因此要删除一次以点u为LCA的路径贡献,再统计答案。统计答案之后,该路径也不再会对u的任意祖先节点产生贡献,因此删去它在权值线段树中的另一次贡献。
实现时可用vector保存以某点为T/LCA的路径。
注意2)操作的权值可能为负,所以左右二式均加上N+1即可。
Code
#include<cstdio>
#include<vector>
using namespace std;
const int sm = 3e5+5;
const int sn = 2400000;
int N,M,tot,lim;
int Rt[2][sm],cnt[2][sn],Ls[2][sn],Rs[2][sn],Lc[sm];
int w[sm],S[sm],T[sm],tp,stk[sn],sum[sm],Ans[sm];//bzoj数据较强需要把stk开到比较大 否则WA
//sum[i]记从i点出发的路径条数
int Fa[sm][22],dep[sm],hd[sm],to[sm<<1],nxt[sm<<1];
vector<int > t[sm],lc[sm];
//t[i]记在i点结束的路径编号 lc[i]记以i为lca的路径编号
template <typename T> void read(T &x) {
char ch=getchar();x=0;
while(ch>'9'||ch<'0') ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
}
void Swap(int &x,int &y) { int t=x;x=y;y=t; }
void Ins(int x,int y) {
to[++tot]=y,nxt[tot]=hd[x],hd[x]=tot;
to[++tot]=x,nxt[tot]=hd[y],hd[y]=tot;
}
void Dfsa(int x,int fa) {
Fa[x][0]=fa,dep[x]=dep[fa]+1;
for(int i=1;i<=19;++i)
if(Fa[x][i-1]) Fa[x][i]=Fa[Fa[x][i-1]][i-1];
else break;
for(int i=hd[x];i;i=nxt[i])
if(to[i]!=fa) Dfsa(to[i],x);
}
int Lca(int u,int v) {
if(dep[u]!=dep[v]) {
if(dep[u]<dep[v]) Swap(u,v);
for(int i=19;i>=0;--i)
if(dep[Fa[u][i]]>dep[v])
u=Fa[u][i];
u=Fa[u][0];
}
if(u==v)return v;
for(int i=19;i>=0;--i)
if(Fa[u][i]!=Fa[v][i])
u=Fa[u][i],v=Fa[v][i];
return Fa[u][0];
}
void del(int &x,int k) {
stk[++tp]=x,Ls[k][x]=Rs[k][x]=cnt[k][x]=0,x=0;
}
int Merge(int x,int &y,int k) {
if(!(x*y))return x+y;
cnt[k][x]+=cnt[k][y];
Ls[k][x]=Merge(Ls[k][x],Ls[k][y],k);
Rs[k][x]=Merge(Rs[k][x],Rs[k][y],k);
del(y,k);
return x;
}
void Add(int &rt,int l,int r,int p,int val,int k) {
if(!rt) { rt = tp? stk[tp--] : ++tot;}
cnt[k][rt]+=val;
if(l==r) { if(!cnt[k][rt])del(rt,k); return; }
int m=(l+r)>>1;
if(p<=m) Add(Ls[k][rt],l,m,p,val,k);
else Add(Rs[k][rt],m+1,r,p,val,k);
if(!Ls[k][rt]&&!Rs[k][rt])del(rt,k);
}
int Query(int rt,int l,int r,int p,int k) {
if(!rt)return 0;
if(l==r) return cnt[k][rt];
int m=(l+r)>>1;
if(p<=m) return Query(Ls[k][rt],l,m,p,k);
else return Query(Rs[k][rt],m+1,r,p,k);
}
void Dfsb(int x,int fa) {
for(int i=hd[x];i;i=nxt[i]) {
if(to[i]!=fa) Dfsb(to[i],x); else continue;
Rt[0][x]=Merge(Rt[0][x],Rt[0][to[i]],0);
Rt[1][x]=Merge(Rt[1][x],Rt[1][to[i]],1);
}
if(sum[x]) Add(Rt[0][x],1,lim,dep[x],sum[x],0);
for(int i=0,p;i<t[x].size();++i) {
p=N+1+dep[S[t[x][i]]]-(dep[Lc[t[x][i]]]<<1);
Add(Rt[1][x],1,lim,p,1,1);
}
Ans[x]+=Query(Rt[0][x],1,lim,w[x]+dep[x],0);
for(int i=0,p;i<lc[x].size();++i) { //删除以点x为Lca的路径
Add(Rt[0][x],1,lim,dep[S[lc[x][i]]],-1,0);
Add(Rt[1][x],1,lim,dep[S[lc[x][i]]]-(dep[x]<<1)+N+1,-1,1);
}
Ans[x]+=Query(Rt[1][x],1,lim,w[x]-dep[x]+N+1,1);
}
int main() {
read(N),read(M),lim=N<<1|1;
for(int i=1,u,v;i<N;++i)
read(u),read(v),Ins(u,v);
for(int i=1;i<=N;++i) read(w[i]);
for(int i=1;i<=M;++i) {
read(S[i]),read(T[i]);
sum[S[i]]++,t[T[i]].push_back(i);
}
tot=0,Dfsa(1,0);
for(int i=1;i<=M;++i)
lc[Lc[i]=Lca(S[i],T[i])].push_back(i);
Dfsb(1,0);
printf("%d",Ans[1]);
for(int i=2;i<=N;++i)
printf(" %d",Ans[i]);
return 0;
}
更新一波两颗线段树节点不需区分的代码
#include<vector>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int sn = 3e5 + 19;
const int sm = 35e6 + 19;
int n,m,lim,tot;
int w[sn],ans[sn];
int to[sn<<1],nxt[sn<<1],hd[sn],dep[sn];
int f[sn][20],S[sn],T[sn],lca[sn],st[sn];
vector<int> tt[sn],lc[sn];
void Swap(int &x,int &y) { int t = x; x = y; y = t; }
void Add(int u,int v) {
to[++tot] = v, nxt[tot] = hd[u], hd[u] = tot;
to[++tot] = u, nxt[tot] = hd[v], hd[v] = tot;
}
#define mid ((l+r)>>1)
namespace St {
int Rt[2][sn],cnt;
int val[sm],Ls[sm],Rs[sm];
void Ins(int &rt,int l,int r,int pos,int k) {
if(!rt) rt = ++cnt;
val[rt] += k;
if(l == r) return;
if(pos <= mid) Ins(Ls[rt],l,mid,pos,k);
else Ins(Rs[rt],mid+1,r,pos,k);
}
int Query(int rt,int l,int r,int pos) {
if(!rt) return 0;
if(l == r) return val[rt];
if(pos <= mid) return Query(Ls[rt],l,mid,pos);
else return Query(Rs[rt],mid+1,r,pos);
}
int Merge(int Rt,int rt) {
if(!(Rt * rt)) return Rt + rt;
val[Rt] += val[rt];
Ls[Rt] = Merge(Ls[Rt],Ls[rt]);
Rs[Rt] = Merge(Rs[Rt],Rs[rt]);
return Rt;
}
}
using namespace St;
void Dfs1(int x,int fa) {
f[x][0] = fa, dep[x] = dep[fa]+1;
for(int i = 1; i <= 19; ++i)
if(f[x][i-1]) f[x][i] = f[f[x][i-1]][i-1];
else break;
for(int i = hd[x]; i; i = nxt[i])
if(to[i]!=fa) Dfs1(to[i],x);
}
int Lca(int u,int v) {
if(dep[u] < dep[v]) Swap(u,v);
for(int i = 19; i >= 0; --i)
if(dep[f[u][i]] >= dep[v])
u = f[u][i];
if(u == v) return v;
for(int i = 19; i >= 0; --i)
if(f[u][i] != f[v][i])
u = f[u][i], v = f[v][i];
return f[u][0];
}
void Dfs2(int x) {
for(int i = hd[x]; i; i = nxt[i])
if(to[i] != f[x][0]) {
Dfs2(to[i]);
Rt[0][x] = Merge(Rt[0][x],Rt[0][to[i]]);
Rt[1][x] = Merge(Rt[1][x],Rt[1][to[i]]);
}
if(st[x]) Ins(Rt[0][x],1,lim,dep[x],st[x]);
int loc;
for(int i = 0,num; i < tt[x].size(); ++i) {
num = tt[x][i];
loc = dep[S[num]]-(dep[lca[num]]<<1)+n+1;
Ins(Rt[1][x],1,lim,loc,1);
}
ans[x] = Query(Rt[1][x],1,lim,w[x]-dep[x]+n+1);
for(int i = 0,num; i < lc[x].size(); ++i) {
num = lc[x][i];
loc = dep[S[num]]-(dep[x]<<1)+n+1;
Ins(Rt[1][x],1,lim,loc,-1);
Ins(Rt[0][x],1,lim,dep[S[num]],-1);
}
ans[x] += Query(Rt[0][x],1,lim,dep[x]+w[x]);
}
int main() {
int u,v;
scanf("%d%d",&n,&m);
for(int i = 1; i < n; ++i)
scanf("%d%d",&u,&v), Add(u,v);
for(int i = 1; i <= n; ++i)
scanf("%d",&w[i]);
for(int i = 1; i <= m; ++i) {
scanf("%d%d",&S[i],&T[i]);
++st[S[i]];
tt[T[i]].push_back(i);
}
Dfs1(1,0);
for(int i = 1; i <= m; ++i)
lc[lca[i] = Lca(S[i],T[i])].push_back(i);
lim = n<<1|1, Dfs2(1);
for(int i = 1; i <= n; ++i)
printf("%d ",ans[i]);
putchar(10);
return 0;
}