[CF966E] May Holidays(树上数据结构问题、分块+虚树)

Description

一个 n 个结点的有根树,每个结点 x 有一个值 t x t_x tx,和一个颜色 (黑/白)。
m 次操作:

  • 翻转某点颜色。
  • 询问有多少点 x 满足:x 为黑色,x 的白色后代数 > tx。
    n, m ≤ 1 0 5 10^5 105

Solution

  • 令 wi = ti− 子树内白点数,那么询问就是 wi < 0 的黑点数,修改就是翻转颜色并将至根路径的w值+1/ − 1。
  • 思来想去好像没有什么奇妙的数据结构能在 O ( n l o g n ) O(nlogn) O(nlogn) O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)内解决这样的问题,于是想到分块。
  • 考虑对询问分块。
  • 使用对询问分块的算法的条件是:
  1. 现在处理的询问块 之前的询问的影响 可以在 O ( n ) O(n) O(n)时间内求出。显然可以满足,处理好每一块的询问后作好标记,处理下一块询问前把整张图再扫一遍即可。
  2. 处理单独一个块内询问的复杂度应该为均摊一次 O ( B ) O(B) O(B)(其中B为块大小)。
  • 对于第二个条件,因为受询问影响的点只有询问点的链并,所以先构建块内所有询问点的链并,实际上就是一棵虚树,只不过1号点一定要存在在虚树中(因为是链并),显然构建的复杂度是 O ( B l o g B ) O(BlogB) O(BlogB)
  • 虚树中同一条边上的点(原树上 处于虚树上相邻两点之间的 那些点),在所有的操作中它们的wi的变化量都是相同的,那么我们可以将它们放到一起处理。
  • 具体来说,对虚树上每条边建一个vector,vector中存处在这条边上的原树上的点,把点按w值排好序,把w值相同的合并到一起,维护一个指针指向最后一个小于0的位置。每次操作后,指针最多平移一位,就可以维护这条边上的点对答案的贡献了。
  • 排序可以用桶排或基数排序优化。
  • 时间复杂度为 O ( n 2 B + n B ) O(\frac {n^2}B+nB) O(Bn2+nB),显然当 B = n B=\sqrt n B=n 时时间复杂度取最优,为 O ( n n ) O(n\sqrt n) O(nn )

Code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<cmath>
using namespace std;
int read(){
    
    
    int x=0,f=1;
	char ch=getchar();
    while (ch<'0'||ch>'9'){
    
    if(ch=='-')f=-1;ch=getchar();}
    while (ch>='0'&&ch<='9'){
    
    x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
const int N=100005;
struct Edge{
    
    int v,nxt;}edge[N*2];
int n,m,cnt,head[N],t[N],ans[N];
int siz[N],top[N],dep[N],fa[N],ind,dfn[N],mx[N];
int tot,a[N],que[N],stack[N];
bool ins[N],col[N];
//col:颜色
//ins:是否为虚树上点
int b[N*2],c[N],bel[N];
//b[i+n]记录<=i的t值个数
//c[i]表示按t值排序后的第i位
//bel:对于虚树上每个点把它在实树里到虚树父亲的路径上的点全部提出来,标记成它的编号
vector<int> stk,vec[N];
//vec:记录虚树上每个点在实树里到虚树父亲的路径
int p1[N],p2[N];
//p1:vec中的t值(已去重) p2[i]:vec中t值=p1[i]且为白点的点个数 
void addedge(int u,int v){
    
    
    edge[++cnt].v=v;edge[cnt].nxt=head[u];head[u]=cnt;
}
void dfs1(int x){
    
    
    dep[x]=dep[fa[x]]+1;
	dfn[x]=++ind;
	siz[x]=1;
    for (int i=head[x];i;i=edge[i].nxt)
        dfs1(edge[i].v),siz[x]+=siz[edge[i].v];
    mx[x]=ind;
}
void dfs2(int x,int tp){
    
    
    top[x]=tp;
	int k=0;
    for(int i=head[x];i;i=edge[i].nxt)
        if(siz[edge[i].v]>siz[k]) k=edge[i].v;
    if(k) dfs2(k,tp);
    for(int i=head[x];i;i=edge[i].nxt)
        if (edge[i].v!=k) dfs2(edge[i].v,edge[i].v);
}
int get_lca(int x,int y){
    
    
    while (top[x]!=top[y]){
    
    
        if (dep[top[x]]<dep[top[y]]) swap(x,y);
        x=fa[top[x]];
    }
    return dep[x]<dep[y]?x:y;
}
bool cmp(int x,int y){
    
    
    return dfn[x]<dfn[y];
}
void build(){
    
    
    sort(a+1,a+tot+1,cmp);
    int top=0;stack[++top]=1;stk.push_back(1);
    for(int i=1;i<=tot;i++){
    
    
        if(a[i]==stack[top]) continue;
        int lca=get_lca(a[i],stack[top]);
        if(lca==stack[top]) {
    
    stack[++top]=a[i];stk.push_back(a[i]);continue;}
        while (dep[stack[top-1]]>=dep[lca]) top--;
        if(dep[stack[top]]>dep[lca]) top--;
        if(stack[top]!=lca) stack[++top]=lca,stk.push_back(lca);
        stack[++top]=a[i];stk.push_back(a[i]);
    }
    while(top>1) top--;
}

void work(int l,int r){
    
    
	for(int i=1;i<=n*2;i++) b[i]=0;
    for(int i=1;i<=n;i++) b[t[i]+n]++,bel[i]=0;
    for(int i=1;i<=n*2;i++) b[i]+=b[i-1];
    for(int i=1;i<=n;i++) c[b[t[i]+n]--]=i;
    ins[0]=1;
    for(int i=0;i<stk.size();i++) ins[stk[i]]=1;
    for(int i=0;i<stk.size();i++)
        for(int j=fa[stk[i]];!ins[j];j=fa[j])
            bel[j]=stk[i];
    int w=0;
    for(int i=1;i<=n;i++)
        if(bel[c[i]]) vec[bel[c[i]]].push_back(c[i]);//这样存到vec中就已经按t值排好序了 
        else w+=(!col[c[i]]&&t[c[i]]<0&&!ins[c[i]]&&!bel[c[i]]);//这波修改是不会对不在虚树边上的点造成影响的 
    //处理不在虚树边上的点 
    for(int i=l;i<=r;i++) ans[i]=w;
    //逐一处理虚树中同一条边上的点 
    for(int i=0;i<stk.size();i++){
    
    
        int sz=0,x=stk[i];
        for(int j=0;j<vec[x].size();j++)
            if(sz&&p1[sz]==t[vec[x][j]]) p2[sz]+=(!col[vec[x][j]]);
            else p1[++sz]=t[vec[x][j]],p2[sz]=(!col[vec[x][j]]);
        int pts=0,now=0,tag=0;
		//now:vec中对答案有贡献的点的个数 
        while(pts<sz&&p1[pts+1]<0) pts++,now+=p2[pts];
        for(int j=l;j<=r;j++){
    
    
            col[que[j]]^=1;
            if(dfn[que[j]]>=dfn[x]&&dfn[que[j]]<=dfn[x]+siz[x]-1){
    
    
				t[x]+=(!col[que[j]])?1:-1;
                tag+=(!col[que[j]])?1:-1;
            }
            while(pts<sz&&p1[pts+1]+tag<0) pts++,now+=p2[pts];
            while(pts&&p1[pts]+tag>=0) now-=p2[pts],pts--;
            //两句while分别针对两种情况,一句都不能少 
            ans[j]+=now;
            if(t[x]<0&&!col[x]) ans[j]++;
        }
        for(int j=l;j<=r;j++) col[que[j]]^=1;//复原 
        for(int j=0;j<vec[x].size();j++) t[vec[x][j]]+=tag;//更新 
    }
    for(int i=l;i<=r;i++) col[que[i]]^=1;//更新 
}
void clear(){
    
    
    for(int i=0;i<stk.size();i++){
    
    
        int x=stk[i];
        vec[x].clear();
        ins[x]=0;
    }
    stk.clear();
}
int main(){
    
    
    n=read();m=read();
    for(int i=2;i<=n;i++) fa[i]=read(),addedge(fa[i],i);
    for(int i=1;i<=n;i++) t[i]=read();
    for(int i=1;i<=m;i++) que[i]=abs(read());
    dfs1(1);
	dfs2(1,1);
    int B=sqrt(m);
    for (int i=1;i<=m;i+=B){
    
    
        int tmp=cnt;
        tot=0;
        for (int j=0;j<B&&i+j<=m;j++) a[++tot]=que[i+j];//que必须保持输入顺序,只能拿a[]排序建虚树 
        build();
        work(i,min(m,i+B-1));
        cnt=tmp;
        clear();
    }
    for(int i=1;i<=m;i++) printf("%d ",ans[i]);
    return 0;
}

参考文章:
https://blog.csdn.net/qq_33229466/article/details/80331803
https://blog.csdn.net/Maxwei_wzj/article/details/81950058

猜你喜欢

转载自blog.csdn.net/Emma2oo6/article/details/113652790