[USACO19DEC]Milk Visits G
题目链接: https://www.luogu.com.cn/problem/P5838
这道题的离线做法的题解还是比较多的,这里介绍一种在线做法。暴力一点,我们可以对于个节点都建一棵权值线段树。这颗权值线段树记录从这个节点到根节点的途中每一个品种奶牛的数量。空间肯定是爆的,但是我们可以利用可持续化线段树优化空间的方法:重复利用某些不被更新的节点。这题不需要在查询中还要修改,我们直接使用普通的主席树就好了,对于每个节点都有一个单点更新,查询也是走到需要访问的权值,根据 \(a,b,father_{lca(a,b)}\) 这三颗树进行计算来得到这条路径上是否要我们要的那个权值。还是比较简单的一道题,主席树 + LCA 模板。
这种主席树 + LCA 的方法对于解决一些树上问题还是比较实用的,每个节点建一棵主席树的方法在初见时也不难想到,这里再推荐一道题目:Count on the tree
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5+5;
int n,m,w[MAXN],f[MAXN][20],dep[MAXN],lg[MAXN];
int rt[MAXN],tot;
vector <int> e[MAXN];
struct Tree
{
int v,ls,rs;
}t[MAXN*20];
void build(int &k,int l,int r)
{
k=++tot;t[k].v=0;
if(l==r) return ;
int mid=l+r>>1;
build(t[k].ls,l,mid);
build(t[k].rs,mid+1,r);
}
int upd(int k,int l,int r,int x)
{
int now=++tot;
t[now]=t[k];
t[now].v++;
if(l==r) return now;
int mid=l+r>>1;
if(x<=mid) t[now].ls=upd(t[now].ls,l,mid,x);
else t[now].rs=upd(t[now].rs,mid+1,r,x);
return now;
}
int query(int a,int b,int c,int l,int r,int x)
{
if(l==r) return (int)(t[a].v+t[b].v-t[c].v*2>0);
int mid=l+r>>1;
if(x<=mid) return query(t[a].ls,t[b].ls,t[c].ls,l,mid,x);
else return query(t[a].rs,t[b].rs,t[c].rs,mid+1,r,x);
}
void dfs(int k,int fa)
{
rt[k]=upd(rt[fa],1,n,w[k]);
f[k][0]=fa;dep[k]=dep[fa]+1;
for(int i=1;i<=lg[dep[k]];++i)
f[k][i]=f[f[k][i-1]][i-1];
for(int i=0;i<e[k].size();++i)
{
int to=e[k][i];
if(to==fa) continue;
dfs(to,k);
}
}
int lca(int x,int y)
{
if(dep[x]<dep[y]) swap(x,y);
while(dep[x]>dep[y]) x=f[x][lg[dep[x]-dep[y]]-1];
if(x==y) return x;
for(int i=lg[dep[x]];i>=0;--i)
{
if(f[x][i]!=f[y][i])
x=f[x][i],y=f[y][i];
}
return f[x][0];
}
int main()
{
scanf("%d %d",&n,&m);
for(int i=1;i<=n;++i) lg[i]=lg[i-1]+(1<<lg[i-1]==i);
for(int i=1;i<=n;++i)
scanf("%d",&w[i]);
for(int i=1;i<n;++i)
{
int u,v;
scanf("%d %d",&u,&v);
e[u].push_back(v);e[v].push_back(u);
}
build(rt[0],1,n);
dfs(1,0);
while(m--)
{
int a,b,c;
scanf("%d %d %d",&a,&b,&c);
printf("%d",query(rt[a],rt[b],rt[f[lca(a,b)][0]],1,n,c));
}
return 0;
}