题解 P2137 【Gty的妹子树】

Mr_Spade

2018-07-10 20:54:32

Solution

为什么这道题没有题解啊,那不如我来贡献一发吧。 做完之后去网上搜了一下其它做法,发现居然要树分块,这不是超麻烦的吗。所以我们可以考虑另一种分块方法:对**时间**分块。 对时间分治是需要离线的,但是对时间分块是可以在线的。 我们先可以考虑静态的情况,如果这道题是静态的询问,那么我们只要根据dfs序列建一棵归并树(线段树的一种,每个节点保存这个区间的权值排序过以后的序列),再进行区间查询就可以了。 那么如果在查询之前进行过若干次修改的话,我们可以暴力扫描一遍这些修改,对查询的答案计算贡献。分两种修改讨论: 如果是修改点权,我们只要先查询这个点是否在被查询的点的子树内,再查询它的修改对答案是否有影响就可以了。 如果是添加一个点,也是类似的,查询是否在这棵子树内以及是否对答案有影响即可。 那么如何查询一个点是否在被查询点的子树内呢?我们可以在添加点的时候预先进行倍增处理这个点向上跳$2^i$层的祖先,这样只要用倍增数组往上跳就可以查询了。 综合一下之前的讨论,如果我们在查询之前的修改不超过$\sqrt m$次时,就在归并树上查询后暴力扫描修改计算贡献;如果修改超过了$\sqrt m$次时,我们只要根据修改重建一下归并树就可以清除掉这些修改,可以发现归并树的重建不会超过$\sqrt m$次。 来分析一下复杂度,为了省事我们把$n$和$m$看成是同阶的,瓶颈显然在暴力扫描计算贡献和归并树的重建上,计算贡献时,最多扫描$O(\sqrt n)$次修改,每次需要$O(\log n)$的复杂度(倍增),所以这部分的复杂度是$O(n\sqrt n \log n)$。而重建归并树每次是$O(n\log n)$的,一共$O(\sqrt n)$次,因此这部分的复杂度也是$O(n\sqrt n \log n)$。 因此复杂度是$O(n\sqrt n \log n)$,通过了这道题。 细节可以参考代码注释 ```cpp #include<cstdio> #include<cmath> #include<algorithm> #include<vector> using std::upper_bound; using std::merge; using std::vector; inline int read() { int res=0; char x; while((x=getchar())<'0'||x>'9'); for(;x>='0'&&x<='9';x=getchar()) res=res*10+x-'0'; return res; } inline void write(int x) { if(x>=10) write(x/10); putchar(x%10+'0'); return; } const int N=1e5+5,LGN=25; int n,m,unit,tot,idx,lastans; int w[N],wi[N],id[N],deep[N],size[N]; int p[N][LGN];//倍增数组 int v[N<<1],first[N],next[N<<1]; struct cell { int opt,u,x,y; }; vector<cell> V; struct node { int l,r; vector<int> V; }sgt[N<<2]; void build(int now,int l,int r)//构建归并树 { sgt[now].l=l;sgt[now].r=r; sgt[now].V.clear(); if(l==r) { sgt[now].V.push_back(wi[l]); return; } int mid=(l+r)>>1; build(now<<1,l,mid); build(now<<1|1,mid+1,r); sgt[now].V.resize(r-l+1); merge(sgt[now<<1].V.begin(),sgt[now<<1].V.end(),sgt[now<<1|1].V.begin(),sgt[now<<1|1].V.end(),sgt[now].V.begin()); return; } int query(int now,int l,int r,int k)//查询 { if(sgt[now].l>r||sgt[now].r<l) return 0; if(sgt[now].l>=l&&sgt[now].r<=r) return sgt[now].V.end()-upper_bound(sgt[now].V.begin(),sgt[now].V.end(),k); return query(now<<1,l,r,k)+query(now<<1|1,l,r,k); } inline void add_edge(int from,int to) { tot+=2; v[tot+1]=from;v[tot]=to; next[tot]=first[from];first[from]=tot; next[tot+1]=first[to];first[to]=tot+1; return; } void dfs(int now,int father)//dfs,用于在重建归并树之前更新一些数据 { register int go; id[now]=++idx;size[now]=1; wi[id[now]]=w[now];/*这个是为了方便建归并树*/p[now][0]=father; for(go=first[now];go;go=next[go]) if(v[go]!=father) { deep[v[go]]=deep[now]+1; dfs(v[go],now); size[now]+=size[v[go]]; } return; } inline bool anc(int u,int v)//查询v是否在u的子树内 { int res=v; register int i; if(deep[v]<deep[u]) return 0; for(i=20;~i;i--) if((deep[v]-deep[u])>>i&1) res=p[res][i]; return res==u; } inline void rebuild()//更新并重建 { V.clear(); idx=0;dfs(1,0); build(1,1,n); return; } signed main() { int from,to; int u,x,res; register int i,j; n=read(); for(i=1;i<=n-1;i++) { from=read();to=read(); add_edge(from,to); } for(i=1;i<=n;i++) w[i]=read(); idx=0;dfs(1,0); for(i=1;i<=n;i++) for(j=1;j<=20;j++) p[i][j]=p[p[i][j-1]][j-1]; build(1,1,n); m=read();unit=ceil(sqrt(m)*5);//块大小 while(m--) switch(read()) { case 0: u=read()^lastans;x=read()^lastans; res=query(1,id[u],id[u]+size[u]-1,x); for(i=0;i<(int)V.size();i++) if(V[i].opt==1) { if(((V[i].x>x)^(V[i].y>x))&&anc(u,V[i].u)) res+=(V[i].y>x)?1:-1; } else { if((V[i].y>x)&&anc(u,V[i].u)) res++; } write(lastans=res);putchar('\n'); break; case 1: u=read()^lastans;x=read()^lastans; V.push_back(cell{1,u,w[u],x});w[u]=x; if((int)V.size()>=unit) rebuild(); break; case 2: u=read()^lastans;x=read()^lastans; V.push_back(cell{2,n+1,u,x}); add_edge(u,++n); w[n]=x;deep[n]=deep[u]+1; p[n][0]=u; for(i=1;i<=20;i++) p[n][i]=p[p[n][i-1]][i-1]; if((int)V.size()>=unit) rebuild(); break; } return 0; } ```