luogu P7394 History 题解
做法一,bfs 序
双 log 做法,目前最优解,吊打单 log。
bfs 序的一些性质
-
对于一段单调递增的 bfs 序的对应节点的 k 级祖先的 bfs 序是单调不下降的,证明显然。
-
对于节点 \(x\),他的 \(k\) 级子孙组成的 bfs 序是连续的一段区间。
对于本题
显然当 \(y\) 是奇数的时候答案为 \(0\),每次查询即查询 \(x\) 的 \(\frac{y}{2}\) 级祖先的 \(\frac{y}{2}\) 级子孙中开灯的个数,对于这些点中在 \(x\) 的 \(\frac{y}{2}-1\) 级祖先子树内的节点到 \(x\) 的距离并不是 \(y\),所以再减去其贡献即可。
那么我们可以利用 bfs 序的这两个性质,二分出该连续区间的左右端点,这个复杂度是 \(\log^2\) 的,随后可以用树状数组维护 bfs 序的单点修改区间查询。
对于操作 \(3\),可以建操作树或主席树完成,对于操作树,将操作离线,若为操作 \(3\) 则 \(x\) 向 \(i\) 连边,否则 \(i-1\) 向 \(i\) 连边。最后 dfs 遍历一遍进行相应操作并回溯即可。
#include<bits/stdc++.h>
#define ll long long
#define endl '\n'
#define sort stable_sort
#define pb push_back
#define lowbit(x) (x&-x)
using namespace std;
const int N=1e5+10;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar_unlocked();
for(;!isdigit(c);c=getchar_unlocked()) if(c=='-') z=0;
for(;isdigit(c);c=getchar_unlocked()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
template<typename T,typename ...Tp> inline void read(T &x,Tp &...y){read(x);read(y...);}
template<typename Tp> inline void wt(Tp x){if(x>9)wt(x/10);putchar_unlocked((x%10)+'0');}
template<typename Tp> inline void write(Tp x){if(x<0)putchar_unlocked('-'),x=~x+1;wt(x);}
template<typename T,typename ...Tp> inline void write(T x,Tp ...y){write(x);putchar_unlocked(' ');write(y...);}
int n,m,tot,c[N],to[N],bfn[N],pos[N],dep[N],yet[N],ans[N],fa[N][20];
bool a[N]; vector<int>e[N],g[N];
struct aa {int op,x,y;}que[N];
void add(int x) {for(x++;x<=n+1;x+=lowbit(x)) c[x]++;}
void del(int x) {for(x++;x<=n+1;x+=lowbit(x)) c[x]--;}
int ask(int x) {int res=0; for(x++;x;x-=lowbit(x)) res+=c[x]; return res;}
int ask(int l,int r) {return ask(r)-ask(l-1);}
void bfs()
{
queue<int>q; q.push(1);
while(!q.empty())
{
int x=q.front(); q.pop(),dep[pos[bfn[x]=++tot]=x]=dep[fa[x][0]]+1;
for(int i=1;i<=__lg(dep[x])+1;i++) fa[x][i]=fa[fa[x][i-1]][i-1];
for(int y:e[x]) if(!bfn[y]) q.push(y),fa[y][0]=x;
}
}
int find(int x,int k)
{
if(dep[x]-1<k) return 0;
for(int i=__lg(dep[x])+1;i>=0;i--) if((k>>i)&1) x=fa[x][i];
return bfn[x];
}
int solve(int x,int k)
{
int l=1,r=n,pre=0,suf=0;
while(l<=r)
{
int mid=l+r>>1;
if(find(pos[mid],k)>=x) pre=mid,r=mid-1;
else l=mid+1;
}
l=1,r=n;
while(l<=r)
{
int mid=l+r>>1;
if(find(pos[mid],k)<=x) suf=mid,l=mid+1;
else r=mid-1;
}
return ask(pre,suf);
}
void dfs(int i)
{
int op=que[i].op,x=que[i].x,y=que[i].y;
if(op==1) a[x]?del(bfn[x]):add(bfn[x]),a[x]^=1;
else if(op==2)
{
if(y&1) ans[i]=0; else if(!y) ans[i]=a[x];
else if(dep[x]-1<(y>>=1)) ans[i]=0;
else ans[i]=solve(find(x,y),y)-solve(find(x,y-1),y-1);
}
for(int j:g[i]) dfs(j);
if(op==1) a[x]?del(bfn[x]):add(bfn[x]),a[x]^=1;
}
signed main()
{
read(n);
for(int i=1,x,y;i<n;i++) read(x,y),e[x].pb(y),e[y].pb(x);
bfs(); read(m);
for(int i=1,op,x,y;i<=m;i++)
{
read(op,x); if(op==2) read(y);
op!=3?g[i-1].pb(i):g[x].pb(i),que[i]={op,x,y};
}
dfs(0);
for(int i=1;i<=m;i++) if(que[i].op==2) write(ans[i]),puts("");
}
做法二,dfs 序
单 log 做法,但是线段树维护常数较大。
dfs 序的一些性质
对于节点 x,他的子树上所有节点构成的 dfs 序是连续的一段区间。
对于本题
做法一说过的这里不再说,只是如何维护的区别。
给每个深度开一棵 dfs 序的动态开点线段树,那么对于查询操作,即在对应深度的线段树上查询 \(x\) 的 \(\frac{y}{2}\) 级祖先的子树对应的 dfs 序区间。
同样需减去 \(x\) 的 \(\frac{y}{2}-1\) 级祖先贡献,建操作树或主席树维护操作 \(3\)。
#include<bits/stdc++.h>
#define ll long long
#define endl '\n'
#define sort stable_sort
#define pb push_back
using namespace std;
const int N=1e5+10,M=2e6+10;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar_unlocked();
for(;!isdigit(c);c=getchar_unlocked()) if(c=='-') z=0;
for(;isdigit(c);c=getchar_unlocked()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
template<typename T,typename ...Tp> inline void read(T &x,Tp &...y){read(x);read(y...);}
template<typename Tp> inline void wt(Tp x){if(x>9)wt(x/10);putchar_unlocked((x%10)+'0');}
template<typename Tp> inline void write(Tp x){if(x<0)putchar_unlocked('-'),x=~x+1;wt(x);}
template<typename T,typename ...Tp> inline void write(T x,Tp ...y){write(x);putchar_unlocked(' ');write(y...);}
int n,m,tot,rt[N],ls[M],rs[M],in[N],out[N],dep[N],val[M],ans[N],fa[N][20];
struct aa {int op,x,y;}q[N]; vector<int>e[N],g[N]; bool a[N];
void change(int &p,int l,int r,int x,int d)
{
if(!p) p=++tot;
if(l==r) return val[p]+=d,void();
int mid=l+r>>1;
x<=mid?change(ls[p],l,mid,x,d):change(rs[p],mid+1,r,x,d);
val[p]=val[ls[p]]+val[rs[p]];
}
int ask(int p,int l,int r,int vl,int vr)
{
if(!p) return 0;
if(vl<=l&&vr>=r) return val[p];
int res=0,mid=l+r>>1;
if(vl<=mid) res+=ask(ls[p],l,mid,vl,vr);
if(vr>mid) res+=ask(rs[p],mid+1,r,vl,vr);
return res;
}
int ask(int x,int k) {return ask(rt[k],1,n,in[x],out[x]);}
void dfs(int x,int t)
{
in[x]=++tot,dep[x]=dep[fa[x][0]=t]+1;
for(int i=1;i<=__lg(dep[x]);i++) fa[x][i]=fa[fa[x][i-1]][i-1];
for(int y:e[x]) if(y!=t) dfs(y,x); out[x]=tot;
}
int find(int x,int k)
{
if(dep[x]-1<k) return 0;
for(int i=__lg(dep[x]);~i;i--) if((k>>i)&1) x=fa[x][i];
return x;
}
void solve(int i)
{
int op=q[i].op,x=q[i].x,y=q[i].y,tmp;
if(op==1) change(rt[dep[x]],1,n,in[x],a[x]?-1:1),a[x]^=1;
if(op==2) if(!(y&1))
{
if(!y) ans[i]=a[x];
else if(fa[tmp=find(x,(y>>1)-1)][0])
ans[i]=ask(fa[tmp][0],dep[x])-ask(tmp,dep[x]);
}
for(int j:g[i]) solve(j);
if(op==1) change(rt[dep[x]],1,n,in[x],a[x]?-1:1),a[x]^=1;
}
signed main()
{
read(n);
for(int i=1,x,y;i<n;i++) read(x,y),e[x].pb(y),e[y].pb(x);
dfs(1,0),tot=0; read(m);
for(int i=1,op,x,y;i<=m;i++)
{
read(op,x); if(op==2) read(y);
op!=3?g[i-1].pb(i):g[x].pb(i),q[i]={op,x,y};
}
solve(0);
for(int i=1;i<=m;i++) if(q[i].op==2) write(ans[i]),puts("");
}