CF463E Caisa and Tree

树链剖分真可爱。

题目传送门

  • 给出 \(n\) 个点以 \(1\) 为根的树,点 \(u\) 有点权 \(a_u\)\(m\) 次操作,两种类型:

    • \(1\texttt{ }u\),查询点 \(u\) 的一个最近祖先 \(v\),满足 \(\gcd(a_u,a_v)>1\),没有输出 \(-1\)

    • \(2\texttt{ }u\texttt{ }d\),将 \(a_u\leftarrow d\)

  • \(n,m\le 10^5\)\(a_u\le 2\times 10^6\)

「保证 \(2\) 操作的数量不超过 \(50\)」的限制和 \(10\) 秒的时限太不牛了。让我们去掉这个限制并将时限改为 \(2\) 秒,再考虑怎么做。

看到单点修改和路径查询,容易想到树链剖分。考虑到 \(\gcd(a_u,a_v)>1\) 本质上是 \(a_u,a_v\) 有着共同的质因数,所以要先用欧拉筛筛出 \(2\times 10^6\) 内的质数。然后可以枚举这个质因数,分别查询有该质因数的最近祖先,再从其中选出最近的即可。

由于根到某个点路径的 \(dfn\) 序是递增的,考虑通过求最大的 \(dfn\) 序来找到最近祖先。具体地,对每种质数维护一个 set,里面有序存放点权有该质因数的点的 \(dfn\) 序。查询某种质因数时,在跳链的过程中通过 lower_boundupper_bound 二分找到不超过当前点 \(u\) 的最大 \(dfn\) 序(如果 \(u\) 为初始点则是严格小于,因为自己不能选),若该值 \(\ge dfn_{top_u}\),说明该值对应的点在当前重链上,直接返回即可。

对于修改操作,相当于在 \(a_u\) 的质因数的 set 中删除 \(dfn_u\),在 \(y\) 的质因数的 set 中插入 \(dfn_u\)

乍一看分解质因数很暴力,但是我们可以在欧拉筛的时候处理每个数 \(i\) 的最小质因数 \(pre_i\),分解质因数的时候,设当前数为 \(x\),则分解一个 \(pre_{x}\) 出来,再令 \(x\leftarrow \dfrac{x}{pre_{x}}\)。由于质数 \(\ge 2\),因此除以 \(pre_x\) 的操作至多进行 \(\log x\),意味着我们完成了在 \(\mathcal{O}(\log x)\) 的时间复杂度内分解质因数。而且,\(2\times 10^6\) 内的数本质不同的质因数个数不超过 \(7\),考虑 \(2\times 3\times 5\times 7\times 11\times 13\times 17\times 19=9699690\),而我们对于同一个质因数只需要进行一次跳链查询/修改 set,因此单次操作跳链查询/修改 set 次数是常数级别。

设值域为 \(V\),最终的时间复杂度为 \(\mathcal{O}(m\log^2 n)\),空间复杂度为 \(\mathcal{O}(|V|)\)

评测记录

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5,V=2e6+5;
int n,m,a[N],pr[V],tot,cnt,pre[V],siz[N],fa[N],hvy[N],top[N],dfn[N],id[N];
bool notp[V],vis[V];
vector<int>g[N];
set<int>s[V];
template<typename T>void read(T&x){
    x=0;T f=1;char c=getchar();
    for(;!isdigit(c);c=getchar())if(c=='-')f=-1;
    for(;isdigit(c);c=getchar())x=(x<<3)+(x<<1)+c-48;
    x*=f;
}
template<typename T>void write(T x){
    if(x>9)write(x/10);
    putchar(x%10+48);
}
template<typename T>void print(T x,char ed='\n'){
    if(x<0)putchar('-'),x=-x;
    write(x),putchar(ed);
}
void reVis(int val){
    for(int tmp=val;tmp!=1;tmp/=pre[tmp])vis[pre[tmp]]=0;
}
void dfs1(int u){
    ++siz[u];
    for(int v:g[u]){
        if(v==fa[u])continue;
        fa[v]=u,dfs1(v),siz[u]+=siz[v];
    }
}
void dfs2(int u){
    for(int v:g[u]){
        if(v==fa[u])continue;
        if((siz[v]<<1)>siz[u])top[hvy[u]=v]=top[u];
        else top[v]=v;
        dfs2(v);
    }
}
void dfs3(int u){
    id[dfn[u]=++tot]=u;
    for(int tmp=a[u];tmp!=1;tmp/=pre[tmp]){
        if(!vis[pre[tmp]])vis[pre[tmp]]=1,s[pre[tmp]].insert(tot);
    }
    reVis(a[u]);
    if(hvy[u])dfs3(hvy[u]);
    for(int v:g[u]){
        if(v==fa[u]||v==hvy[u])continue;
        dfs3(v);
    }
}
int query(int x,int fac){
    int u=x;
    while(u){
        auto it=(u==x?s[fac].lower_bound(dfn[u]):s[fac].upper_bound(dfn[u]));
        if(it!=s[fac].begin()){
            --it;
            if(*it>=dfn[top[u]])return*it;
        }
        u=fa[top[u]];
    }
    return-1;
}
signed main(){
    for(int i=2;i<V;++i){
        if(!notp[i])pr[++cnt]=pre[i]=i;
        for(int j=1;i*pr[j]<V;++j){
            notp[i*pr[j]]=1,pre[i*pr[j]]=pr[j];
            if(!(i%pr[j]))break;
        }
    }
    read(n),read(m);
    for(int i=1;i<=n;++i)read(a[i]);
    for(int i=1,u,v;i<n;++i){
        read(u),read(v);
        g[u].emplace_back(v),g[v].emplace_back(u);
    }
    dfs1(1),dfs2(top[1]=1),dfs3(1);
    for(int i=1,op,x,y;i<=m;++i){
        read(op),read(x);
        if(op==1){
            int ret=-1;
            for(int tmp=a[x];tmp!=1;tmp/=pre[tmp]){
                if(!vis[pre[tmp]])vis[pre[tmp]]=1,ret=max(ret,query(x,pre[tmp]));
            }
            print(ret==-1?-1:id[ret]);
            reVis(a[x]);
        }else{
            read(y);
            for(int tmp=a[x];tmp!=1;tmp/=pre[tmp]){
                if(!vis[pre[tmp]])vis[pre[tmp]]=1,s[pre[tmp]].erase(dfn[x]);
            }
            reVis(a[x]);
            a[x]=y;
            for(int tmp=y;tmp!=1;tmp/=pre[tmp]){
                if(!vis[pre[tmp]])vis[pre[tmp]]=1,s[pre[tmp]].insert(dfn[x]);
            }
            reVis(y);
        }
    }
    return 0;
}
posted @ 2023-09-06 08:02  lzyqwq  阅读(38)  评论(0)    收藏  举报