Top Cluster分块 & 莫队二次离线

· · 算法·理论

你确定不是 top cluster?|审核管理员:MatrixGroup

我也不知道为什么要把这两个放到一起写。

Top Cluster

Top Cluster 会将树划分为满足一下性质的若干个

偷张图:

算法流程:

对原树进行 dfs,同时维护当前点 u 的每个子树 v 中待匹配点的个数。

我们会让当前节点 u 成为待匹配的界点当且仅当:

之后,我们贪心的确定若干以 u 为上界点的簇。

// bn 是待匹配界点,wat 是待匹配点
void par(int u){
    ctp[u]=tp;wat[u]=1;
    int cc=0;
    for(auto [v,w]:adj[u])  if(v!=fa[u]){
        st[++tp]=v;par(v);
        wat[u]+=wat[v];
        if(bn[v]){
            bn[u]=bn[v];cc++;
        }
    }
    if(wat[u]>B || cc>1 || u==1){                  //判断
        wat[u]=0;bn[u]=u;adj[u].pb({0,0});
        int cwat=0,cd=0,j=ctp[u]+1;
        ctp[0]=inf;
        for(auto [v,_]:adj[u])  if(v!=fa[u] || !fa[u]){
            if(cwat+wat[v]>B || (cd && bn[v]) || !v){           
                for(;j<=min(tp,ctp[v]-1);j++) cur[++ccc]=st[j];  //贪心进行匹配
                add_cl(u,cd);
                cwat=cd=0;
            }
            cwat+=wat[v];
            if(bn[v])   cd=bn[v];
        }
        adj[u].pop_back();
        tp=ctp[u];
    }
}

加入一个簇的过程是简单的:

void add_cl(int u,int v){
    if(v){
        FA[v]=u;key.pb(v);
        near[u]=u;
        for(int t=v;t!=u;t=fa[t])   near[t]=t;
    }
    cclu++;
    rep(i,1,ccc,1){
        int r=cur[i];clu[cclu].pb(r);cid[r]=cclu;down[r]=v;up[r]=u;
        if(!v){near[r]=u;continue ;}
        int w=0;for(w=r;!near[w];w=fa[w]);
        near[r]=near[w];
    }
    if(v)   up[v]=v;
    ccc=0;
}

tdnmo

考虑对树做 Top-Cluster 分块。

若一个询问点不在收缩树上,或者它没有下界点,则根据簇的性质,我们知道,这个点的有向邻域大小一定不超过 B,我们直接从 N(1,0) 移动过去即可。

否则,我们考虑将询问挂到它所在的下界点上,不妨设为 b,则我们每次先跳到 b 所在的邻域上,再跳回至 u

b 的邻域跳到 u 至多产生 B 的代价(簇中点可能会重复计算),共有 \dfrac{n}{B} 个邻域,每个邻域一共产生 O(n) 的代价,取 B=\sqrt{n}, 既可以用 O(n\sqrt{n}) 的代价解决这个题。

莫队二次离线。

常规莫队中,我们每次移动 [l,r][l,r+1],并计算贡献。

注意到我们每次移动端点都是一段区间:不妨设我们从 [l,r] 移动至 [l,R]

如果贡献可差分,我们可以把它差分成这样:(不妨记 f(l,r,x)[l,r]x 产生的贡献)

\sum_{i=r+1}^{R}(f(1,i-1,i)-f(1,l-1,i)) 容易发现,我们一共产生了 $O(n)$ 次修改和 $O(n\sqrt{q})$ 次查询,这给了我们使用分块算法平衡复杂度的契机。 **人话:二次离线是用分块平衡掉 $\log$ 的。** ### [Range Pairs Distance Query(rpdq)](https://www.luogu.com.cn/problem/P6778) 对的对的,应该没错吧?。 先二次离线,然后用经典套路(LCA 那个题),把他改成 $O(n)$ 次路径加,$O(n\sqrt{q})$ 次路径和。 树链剖分或者全局平衡二叉树什么的就别想过了。 对原树 Top Cluster 分块。维护以下信息: - 界点的前缀和 - 簇内每个点到这个簇的上界点的和 - 收缩树上的边被经过过几次 这样修改是 $O(\sqrt{n})$ 的,查询是 $O(1)$ 的,你就全对了。 ```cpp void padd(int u){ if(u==1) return ; for(auto v:key) sua[v]-=sua[FA[v]]; if(!FA[u]){ sua[down[u]]+=dep[near[u]]-dep[up[u]]; int ci=cid[u]; per(i,(int)clu[cid[u]].size()-1,0,1){ int v=clu[cid[u]][i]; if(!FA[fa[v]]) sub[v]-=sub[fa[v]]; } for(;!FA[u] && u!=1;u=fa[u]) sub[u]+=dep[u]-dep[fa[u]]; for(auto v:clu[ci]){ if(!FA[fa[v]]) sub[v]+=sub[fa[v]]; } } for(;u!=1;u=FA[u]){ tag[u]++,sua[u]+=dep[u]-dep[FA[u]]; } per(i,(int)key.size()-1,0,1) sua[key[i]]+=sua[FA[key[i]]]; } ``` 顺便提一句,**二次离线在做前缀和的时候一定要注意顺序!!我们使用排序后的 $i$ 而不是原询问 $\text{id}$。** --- NOIp 不考省选不考 NOI 不考秒了喵!!!!!!!!!!!!!!