题面
题解
考虑离线,计算每个钥匙的贡献,根据询问和树上点的
d
f
n
dfn
dfn 值加减贡献。
同种钥匙最多5把,考虑对每种颜色分别处理。
针对每种颜色,枚举钥匙的点
x
x
x ,从
x
x
x 出发dfs,计钥匙为
+
1
+1
+1 ,计宝箱为
−
1
-1
−1 ,若到达某个点
y
y
y 时满足计数为
0
0
0 且
y
y
y 点为宝箱,则找到了一个钥匙和宝箱的匹配。dfs后可以找到每个钥匙的匹配,换句话说,从某个方向拿到钥匙,向某个方向走可以开启对应的宝箱,若询问的路径包含这条路径,则对答案有
1
1
1 的贡献。
记
x
x
x 的
d
f
n
dfn
dfn 值为
d
f
n
[
x
]
dfn[x]
dfn[x] ,
x
x
x 子树内
d
f
n
dfn
dfn 值最大的点的
d
f
n
dfn
dfn 值为
e
d
[
x
]
ed[x]
ed[x] ,能拿到
x
x
x 的钥匙并开启
y
y
y 的宝箱记为有贡献,具体讨论
x
x
x 和
y
y
y 的位置和贡献的关系:
- 若 y y y 在 x x x 的子树内,记 x x x 向 y y y 的方向的儿子为 z z z ,则 [ 1 , d f n [ z ] − 1 ] ⋃ [ e d [ z ] + 1 , n ] [1,dfn[z]-1]\bigcup[ed[z]+1,n] [1,dfn[z]−1]⋃[ed[z]+1,n]的点向 [ d f n [ y ] , e d [ y ] ] [dfn[y],ed[y]] [dfn[y],ed[y]] 的点走时,有贡献。
- 若 x x x 在 y y y 的子树内, 记 y y y 向 x x x 方向的儿子为 z z z ,则 [ d f n [ x ] , e d [ x ] ] [dfn[x],ed[x]] [dfn[x],ed[x]] 的点向 [ 1 , d f n [ z ] − 1 ] ⋃ [ e d [ z ] + 1 , n ] [1,dfn[z]-1]\bigcup[ed[z]+1,n] [1,dfn[z]−1]⋃[ed[z]+1,n] 的点走时,有贡献。
- 若 1 1 1 、 2 2 2 两点均不满足,则 x x x 和 y y y 各在不同的子树内,则 [ d f n [ x ] , e d [ x ] ] [dfn[x],ed[x]] [dfn[x],ed[x]] 的点向 [ d f n [ y ] , e d [ y ] ] [dfn[y],ed[y]] [dfn[y],ed[y]] 的点走时,有贡献。
以上贡献可以由[出发区间,目标区间,贡献系数]的三元组表示,可以差分解决贡献的区间加,用树状树组维护前缀和。根据
d
f
n
dfn
dfn 值离线,从小到大处理差分和询问,即可以得到答案。
具体看一下代码就懂了:
#include<bits/stdc++.h>
#define ll long long
#define uit unsigned int
#define lowbit(x) (x&(-x))
#define pb push_back
using namespace std;
struct IO{
static const int S=1<<21;
char buf[S],*p1,*p2;int st[105],Top;
~IO(){clear();}
inline void clear(){fwrite(buf,1,Top,stdout);Top=0;}
inline void pc(const char c){Top==S&&(clear(),0);buf[Top++]=c;}
inline char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++;}
inline IO&operator >> (char&x){while(x=gc(),x==' '||x=='\n'||x=='\r');return *this;}
template<typename T>inline IO&operator >> (T&x){
x=0;bool f=0;char ch=gc();
while(!isdigit(ch)){if(ch=='-') f^=1;ch=gc();}
while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=gc();
f?x=-x:0;return *this;
}
inline IO&operator << (const char c){pc(c);return *this;}
template<typename T>inline IO&operator << (T x){
if(x<0) pc('-'),x=-x;
do{st[++st[0]]=x%10,x/=10;}while(x);
while(st[0]) pc('0'+st[st[0]--]);return *this;
}
}fin,fout;
const int N=5e5+10,M=1e6+5,MAXP=1e7+1e6+10;
int n,m,pcnt=0,dfc=0,idx,colnow,type[N],col[N],dep[N],f[N][25],tr[N],dfn[N],ed[N],stk[N],top=0,seq[N],dp[N],res[M];
vector<int> e[N],vit[N],colbin[N];
struct P{
int dfctim,l,r,op;//出发点边界dfn,目标区间,操作+/- or 询问
// op=0 -> 区间+ op=-1 -> 区间- op>0 -> 询问编号
friend bool operator<(P x,P y){
if(x.dfctim!=y.dfctim) return x.dfctim<y.dfctim;
return x.op<y.op;
}
}p[MAXP];//操作和询问
void Add(int x,int y){//树状树组函数
for(int i=x;i<=n;i+=lowbit(i)) tr[i]+=y;
}
int Sum(int x){//树状树组函数
int temp=0;
for(int i=x;i;i-=lowbit(i)) temp+=tr[i];
return temp;
}
void dfs(int x,int fa){//预处理dfn等信息
dep[x]=dep[fa]+1;dfn[x]=ed[x]=++dfc;
for(int y:e[x]){
if(y==fa) continue;
f[y][0]=x;
for(int j=1;j<=idx;j++){
f[y][j]=f[f[y][j-1]][j-1];
}
dfs(y,x);
ed[x]=ed[y];
}
}
int lca(int x,int y){
if(dep[x]<dep[y]) swap(x,y);
for(int i=idx;i>=0;i--) if(f[x][i]&&dep[f[x][i]]>=dep[y]) x=f[x][i];
if(x==y) return x;
for(int i=idx;i>=0;i--) if(f[x][i]&&f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
return f[x][0];
}
int last(int rt,int y){//rt向y的方向的儿子
for(int i=idx;i>=0;i--) if(f[y][i]&&dep[f[y][i]]>dep[rt]) y=f[y][i];
return y;
}
bool sort_by_dfn(int x,int y){
return dfn[x]<dfn[y];
}
void ins(int x){//建虚树
if(!top){
stk[top=1]=x;
return ;
}
int A=lca(stk[top],x);
while(top>1&&dep[A]<dep[stk[top-1]]){
vit[stk[top-1]].pb(stk[top]);
vit[stk[top]].pb(stk[top-1]);
top--;
}
if(dep[A]<dep[stk[top]]){
vit[A].pb(stk[top]);
vit[stk[top]].pb(A);
top--;
}
if(!top||stk[top]!=A) stk[++top]=A;
stk[++top]=x;
}
bool intree(int rt,int y){//y是否在rt的子树内
return dfn[rt]<=dfn[y]&&ed[y]<=ed[rt];
}
void report(int x,int y){//根据在树上的位置关系,处理操作,差分贡献
int z;
if(intree(x,y)){
z=last(x,y);
p[++pcnt]={1,dfn[y],ed[y],0};
p[++pcnt]={dfn[z],dfn[y],ed[y],-1};
p[++pcnt]={ed[z]+1,dfn[y],ed[y],0};
}
else if(intree(y,x)){
z=last(y,x);
p[++pcnt]={dfn[x],1,dfn[z]-1,0};
p[++pcnt]={dfn[x],ed[z]+1,n,0};
p[++pcnt]={ed[x]+1,1,dfn[z]-1,-1};
p[++pcnt]={ed[x]+1,ed[z]+1,n,-1};
}
else{
p[++pcnt]={dfn[x],dfn[y],ed[y],0};
p[++pcnt]={ed[x]+1,dfn[y],ed[y],-1};
}
}
void match(int x,int fa,int rt){
dp[x]=dp[fa]+(col[x]==colnow?(type[x]==1?1:-1):0);
if(col[x]==colnow&&type[x]==2&&dp[x]==0){//找到匹配的宝箱就停下
report(rt,x);
return;
}
for(int y:vit[x]){
if(y==fa) continue;
match(y,x,rt);
}
}
void clearvit(int x,int fa){
for(int y:vit[x]){
if(y==fa) continue;
clearvit(y,x);
}
vit[x].clear();
}
void create(int R){
top=0;colnow=R;int len=0;
for(int i=0;i<colbin[R].size();i++){//建虚树
seq[++len]=colbin[R][i];
}
sort(seq+1,seq+len+1,sort_by_dfn);
if(seq[1]!=1) stk[top=1]=1;
for(int i=1;i<=len;i++) ins(seq[i]);
while(top>1){
vit[stk[top]].pb(stk[top-1]);
vit[stk[top-1]].pb(stk[top]);
top--;
}
top=0;
for(int i=1;i<=len;i++){
if(type[seq[i]]==1){
match(seq[i],0,seq[i]);//匹配钥匙和宝箱
}
}
clearvit(seq[1],0);//初始化
}
int main(){
freopen("keys.in","r",stdin);
freopen("keys.out","w",stdout);
fin>>n>>m;idx=log(n)/log(2)+1;
for(int i=1;i<=n;i++){
fin>>type[i]>>col[i];
colbin[col[i]].pb(i);//存到对应的颜色的桶
}
for(int i=1,u,v;i<n;i++){
fin>>u>>v;
e[u].pb(v);//建原边
e[v].pb(u);
}
dfs(1,0);//预处理dfn等信息
for(int i=1,s,e;i<=m;i++){
fin>>s>>e;
p[++pcnt]={dfn[s],dfn[e],0,i};//询问
}
for(int i=1;i<=n;i++){
if(colbin[i].empty()) continue;
create(i);//对每个颜色分别处理
}
sort(p+1,p+pcnt+1);//按dfn序、操作与询问的优先级排序
for(int i=1;i<=pcnt;i++){
if(p[i].op==0){//区间+
Add(p[i].l,1);
Add(p[i].r+1,-1);
}
else if(p[i].op==-1){//区间-
Add(p[i].l,-1);
Add(p[i].r+1,1);
}
else{
res[p[i].op]=Sum(p[i].l);//询问答案
}
}
for(int i=1;i<=m;i++) fout<<res[i],fout.pc('\n');//输出
return 0;
}