数论div2训练题解

训练链接

CF1725E

题目链接

点击打开链接

题目解法

因为 g g g 为质因子个数,所以考虑对于每个质因子分开考虑
然后把包含质因子 p p p 的点建出虚树
然后答案就是要求 ∑ f ( x , y , z ) = ∑ 1 3 ( d ( x , y ) + d ( x , z ) + d ( y , z ) ) = n − 2 2 ∑ d ( x , y ) \sum f(x,y,z)=\sum\frac{1}{3}{(d(x,y)+d(x,z)+d(y,z))}=\frac{n-2}{2}\sum d(x,y) f(x,y,z)=31(d(x,y)+d(x,z)+d(y,z))=2n2d(x,y)
直接树形 d p dp dp 考虑每条边的贡献即可
时间复杂度 O ( n ω a l o g n ) O(n\omega_alogn) O(nωalogn)

#include <bits/stdc++.h>
using namespace std;
const int N=200100;
const int P=998244353,iv2=499122177;
typedef pair<int,int> pii;
int n,val[N];
int depth[N],up[N][20],dfn[N],dfs_clock;
int e[N<<1],ne[N<<1],h[N],idx;
int pr[N],v[N],cnt;
bool tag[N];
vector<int> inc[N];
vector<pii> T[N];
inline int read(){
    int FF=0,RR=1;
    char ch=getchar();
    for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
    for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
    return FF*RR;
}
void sieve(int n){
    for(int i=2;i<=n;i++){
        if(!v[i]) v[i]=i,pr[++cnt]=i;
        for(int j=1;j<=cnt&&pr[j]<=n/i;j++){
            v[pr[j]*i]=pr[j];
            if(v[i]==pr[j]) break;
        }
    }
}
int get_lca(int x,int y){
    if(depth[x]>depth[y]) swap(x,y);
    for(int i=18;i>=0;i--) if(depth[up[y][i]]>=depth[x]) y=up[y][i];
    if(x==y) return x;
    for(int i=18;i>=0;i--) if(up[x][i]!=up[y][i]) x=up[x][i],y=up[y][i];
    return up[x][0];
}
int get_dist(int x,int y){ return depth[x]+depth[y]-2*depth[get_lca(x,y)];}
int siz[N],res,tot;
void calc(int u){
    siz[u]=tag[u];
    for(auto [v,w]:T[u]){
        calc(v);
        // cout<<siz[v]<<' '<<w<<'\n';
        res=(res+1ll*w*siz[v]%P*(tot-siz[v]))%P,siz[u]+=siz[v];
    }
}
int virtual_tree(vector<int> nodes){
    if(nodes.empty()) return 0;
    tot=nodes.size();
    for(int u:nodes) tag[u]=1;
    sort(nodes.begin(),nodes.end(),[](const int &x,const int &y){ return dfn[x]<dfn[y];});
    int len=nodes.size();
    for(int i=0;i<len-1;i++) nodes.push_back(get_lca(nodes[i],nodes[i+1]));
    sort(nodes.begin(),nodes.end(),[](const int &x,const int &y){ return dfn[x]<dfn[y];});
    nodes.erase(unique(nodes.begin(),nodes.end()),nodes.end());
    int rt=nodes[0];
    for(int i=1;i<nodes.size();i++){
        int lca=get_lca(nodes[i],nodes[i-1]),dis=get_dist(nodes[i],lca);
        T[lca].push_back({nodes[i],dis});
    }
    res=0;calc(rt);
    res=1ll*res*(tot-2)%P*iv2%P;
    // cout<<res<<'\n';
    for(int u:nodes) T[u].clear(),tag[u]=0;
    return res;
}
void dfs(int u,int fa){
    dfn[u]=++dfs_clock,depth[u]=depth[fa]+1,up[u][0]=fa;
    for(int i=h[u];~i;i=ne[i]){
        int v=e[i];if(v==fa) continue;
        dfs(v,u);
    }
}
void add(int x,int y){ e[idx]=y,ne[idx]=h[x],h[x]=idx++;}
int main(){
    sieve(200000);
    n=read();
    for(int i=1;i<=n;i++) val[i]=read();
    memset(h,-1,sizeof(h));
    for(int i=1;i<n;i++){
        int x=read(),y=read();
        add(x,y),add(y,x);
    }
    dfs(1,0);
    for(int j=1;j<=18;j++) for(int i=1;i<=n;i++) up[i][j]=up[up[i][j-1]][j-1];
    for(int i=1;i<=n;i++){
        int t=val[i];
        while(t>1){
            inc[v[t]].push_back(i);
            int x=v[t];
            while(t%x==0) t/=x;
        }
    }
    int ans=0;
    for(int i=1;i<=2e5;i++) ans=(ans+virtual_tree(inc[i]))%P;
    printf("%d\n",ans);
    fprintf(stderr,"%d ms\n",int(1e3*clock()/CLOCKS_PER_SEC));
    return 0;
}

CF906D

题目链接

点击打开链接

题目解法

考虑很多指数级的东西,考虑欧拉定理,因为一个数的若干次方 % p \%p %p 的值最多只会关注前 l o g log log 个指数,因为考虑拓展欧拉定理: a x ≡ a x % ϕ ( p ) + ϕ ( p ) ( m o d    p ) a^x\equiv a^{x\%\phi(p)+\phi(p)}(\mod p) axax%ϕ(p)+ϕ(p)(modp),而 p → p h i ( p ) p\to phi(p) pphi(p) 只会衰减 O ( l o g ) O(log) O(log) 次就会变成 1 1 1
然后预处理出每个可能用到的 p h i phi phi,对于询问暴力做时间复杂度就是对的
时间复杂度为 O ( q l o g n ) O(qlogn) O(qlogn)
要注意到快速幂的时候如果 x y ≥ P xy\ge P xyP,要把变成 x y % P + P xy\%P+P xy%P+P,以满足拓展欧拉定理

#include <bits/stdc++.h>
using namespace std;
const int N=100100;
int n,P,l,r,w[N];
map<int,int> mp;
inline int read(){
    int FF=0,RR=1;
    char ch=getchar();
    for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
    for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
    return FF*RR;
}
int getpw(int a,int b,int Mod){
    int res=1;
    for(;b;b>>=1){
        if(b&1){
            if(1ll*res*a>=Mod) res=1ll*res*a%Mod+Mod;
            else res*=a;
        }
        if(1ll*a*a>=Mod) a=1ll*a*a%Mod+Mod;
        else a*=a;
    }
    return res;
}
int ask(int pos,int Mod){
    if(pos>r||Mod==1) return 1;
    int mi=ask(pos+1,mp[Mod]);
    return getpw(w[pos],mi,Mod);
}
void solvephi(int x){
    if(x==1) return;
    int phi=x,t=x;
    for(int i=2;i*i<=x;i++)
        if(x%i==0){
            while(x%i==0) x/=i;
            phi=phi/i*(i-1);
        }
    if(x>1) phi=phi/x*(x-1);
    mp[t]=phi,solvephi(phi);
}
int main(){
    n=read(),P=read();
    solvephi(P);
    for(int i=1;i<=n;i++) w[i]=read();
    int q=read();
    while(q--){
        l=read(),r=read();
        printf("%d\n",ask(l,P)%P);
    }
    fprintf(stderr,"%d ms\n",int(1e3*clock()/CLOCKS_PER_SEC));
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值