题面
题解考虑离线,计算每个钥匙的贡献,根据询问和树上点的
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#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 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 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 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 //树状树组函数 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] =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]//建虚树 if(!top){ stk[top=1]=x; return ; } int A=lca(stk[top],x); while(top>1&&dep[A] vit[stk[top-1]].pb(stk[top]); vit[stk[top]].pb(stk[top-1]); top--; } if(dep[A] 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 //建虚树 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 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<


![[AHOI2022] 钥匙 简要题解 [AHOI2022] 钥匙 简要题解](http://www.mshxw.com/aiimages/31/879171.png)
