P5221 Product 另解

题目链接

推式子的方向不一样,最后呈现的结果不一样。

为了方便,\(\log\) 均表示以原根 \(g\) 为底的离散对数。

\[\begin{aligned} \prod_{i=1}^n\prod_{j=1}^n\frac{\operatorname{lcm}(i,j)}{\gcd(i,j)} &= {\prod_{i=1}^n\prod_{j=1}^n (i \times j)} \times {\prod_{i=1}^n\prod_{j=1}^n \gcd (i,j)^{-2}} \end{aligned} \]

拆开分别考虑

前一项直接处理。

\[\begin{aligned} \prod_{i=1}^n\prod_{j=1}^n (i \times j) &= \prod _{i=1} ^ n i^n \prod _{j=1} ^n j \\ &= (n!)^n \prod _{i=1} ^ n i ^ n \\ &= (n!)^{2n} \end{aligned} \]

后一项看着 \(\prod\) 就烦,考虑取离散对数。(此处省略指数上的 \(-2\)

\[\begin{aligned} \log \prod _{i=1} ^{n} \prod _{j=1} ^n \gcd (i,j) &= \sum _{i=1} ^n\sum_{j=1}^n \log \gcd (i,j) \\&= \sum _{d=1} ^ n \log d \sum _{i=1} ^n \sum _{j=1} ^n [\gcd (i,j)=1] \\&= \sum _{d=1} ^n \log d \sum _{i=1} ^{\left \lfloor \frac{n}{d} \right \rfloor} \sum _{j=1} ^{\left \lfloor \frac{n}{d} \right \rfloor} [\gcd (i,j)=1] \\&= \sum _{d=1}^n \log d \sum _{i=1} ^ {\left \lfloor \frac{n}{d} \right \rfloor} \sum _{j=1} ^ {\left \lfloor \frac{n}{d} \right \rfloor} \sum _{l \mid \gcd (i,j) } \mu (l) \\ &= \sum _{d=1} ^n \log d \sum _{i=1} ^{\left \lfloor \frac{n}{d} \right \rfloor} \sum _{j=1} ^{\left \lfloor \frac{n}{d} \right \rfloor} \sum _{l=1} ^n \mu (l) [l \mid i][l \mid j] \\&= \sum _{d=1} ^n \log d \sum _{l=1} ^n \mu(l) \sum _{i=1} ^{\left \lfloor \frac{n}{d} \right \rfloor} [l \mid i] \sum _{j=1} ^{\left \lfloor \frac{n}{d} \right \rfloor} [l \mid j] \\&= \sum _{d=1} ^n \log d \sum _{l=1} ^ n \mu (l) {\left \lfloor \frac{n}{dl} \right \rfloor}^2 \\&= \sum_{T=1} ^n {\left \lfloor \frac{n}{T} \right \rfloor}^2 \sum _{d \mid T } \log d \times \mu (\frac{T}{d}) \end{aligned} \]

这个式子直接计算枚举 \(d\)\(O(n \ln n)\) 的,前提是每个 \(d\) 只枚举到有用的。但是一般枚举一个数的因子是 \(O(\sqrt n)\) 的,凭空增加复杂度。于是考虑质因数分解优化。发扬人类智慧。

表面上计算每个数的质因数分解是 \(\sum \limits _{i=1} ^n \sqrt i \approx 6\times 10^8\) 的,实际上用筛了质数之后优化,再使用这段程序测算后发现只有 \(1 \times 10^6\)

测试代码
pri =[]


def breakdown(x:int) -> int:
    ans = 0
    for i in pri:
        ans +=1
        if(x%i==0):
            while x%i==0:
                ans+=1
                x//=i
        if(i*i>x): break
    if x>1:ans+=1
    return ans

n=int(1000000)
r=0

not_prime = [False] * n


def pre(n):
    for i in range(2, n + 1):
        if not not_prime[i]:
            pri.append(i)
        for pri_j in pri:
            if i * pri_j > n:
                break
            not_prime[i * pri_j] = True
            if i % pri_j == 0:
                break


for i in range(n):
    r+=breakdown(i)

print(r)

至于求 \([1,n]\) 离散对数,考虑 BSGS 共用哈希表,设往哈希表中插入 \(B\) 个数,只有质数是需要查询哈希表,总共有 \(\frac{nP}{B\ln n}\) 次查询。当 \(B = \sqrt \frac{nP}{\ln n}\) 时有最优复杂度 \(O(\sqrt \frac{nP}{\ln n}) \approx 2 \times 10 ^6\)

然后我们有了 \(1 \sim n\) 的质因数分解,只需要 DFS 遍历所有因子即可。

复杂度比较玄学,注意常数优化。

AC 代码
#include <bits/stdc++.h>
#include <bits/extc++.h>
using namespace std;
const int mod=104857601,G=3,phi=mod-1;
const int N=1e6+10;
const int B=1200000;
const int S=B-1,step=100867123;//step = G^{B-1}
bool __st__;
int pr[N/10];//质数表
bitset<N> vis;
short mu[N];
int lg[N];//以 G 为底的离散对数
int n;
namespace HST{
    const int HSTMOD=2524939;
    struct FastMod{
    	typedef unsigned long long ull;
    	typedef __uint128_t lll;
    	ull b,m;
    	void init(ull _b) {this->b=_b,m=ull((lll(1)<<64)/_b);}
    	ull operator()(ull a){
            if(b==1) return 0;
    		ull q=(ull)((lll(m)*a)>>64),r=a-q*b;
    		return r>=b?r-b:r;
    	}
    }moder;
    struct node{
        int key,val,nxt;
    }pool[B+1];
    int pcnt;
    struct hash_table{
        signed char s;
        int head[HSTMOD];
        void insert(int x,int w){
            int t=moder(x);
            for(int i=head[t];i;i=pool[i].nxt){
                if(pool[i].key==x){
                    pool[i].val=w;
                    return ;
                }
            }
            pcnt++;
            pool[pcnt].key=x;
            pool[pcnt].val=w;
            pool[pcnt].nxt=head[t];
            head[t]=pcnt;
        }
        void init(){
            moder.init(HSTMOD);
        }
        int qry(int x){
            assert(x>=0);
            int t=moder(x);
            for(int i=head[t];i;i=pool[i].nxt){
                if(pool[i].key==x){
                    return pool[i].val;
                }
            }
            return -1;
        }
    };
}
int fpow(int x,int p){
    int res=1;
    while(p){
        if(p&1) res=1ll*res*x%mod;
        x=1ll*x*x%mod;
        p>>=1;
    }
    return res;
}
HST::hash_table hs;//G ^ i -> i 
int BSGS(int b){
    int y=fpow(b,mod-2);
    for(int i=0;;i++){
        if(hs.qry(y)!=-1){
            return i*S-hs.qry(y);
        }
        y=1ll*y*step%mod;
    }
}
void euler(){
    hs.init();
    int x=1;
    {//初始化哈希表
        for(int i=1;i<=B;i++){
            x=x*G%mod;
            hs.insert(x,i);
        }
    }
    int cnt=0;
    for(int i=2;i<N;i++){
        if(!vis.test(i)){
            mu[i]=-1;
            lg[i]=BSGS(i);
            pr[++cnt]=i;
        }
        for(int j=1;pr[j]*i<N;j++){
            vis.set(pr[j]*i);
            lg[pr[j]*i]=lg[pr[j]]+lg[i];
            if(i%pr[j]==0){
                mu[pr[j]*i]=0;
                break;
            }else{
                mu[pr[j]*i]=mu[pr[j]]*mu[i];
            }
        }
    }
    mu[1]=1;
}
int ds[17][2];
int siz;
void breakdown(int x){
    siz=0;
    if(x==1){
        ds[siz][0]=1;
        ds[siz][1]=0;
        siz++;
        return;
    }
    for(int i=1;pr[i]*pr[i]<=x;i++){
        if(x%pr[i]==0){
            int cnt=0;
            while(x%pr[i]==0){
                cnt++;
                x/=pr[i];
            }
            ds[siz][0]=pr[i];
            ds[siz][1]=cnt;
            siz++;
        }
    }
    if(x>1){
        ds[siz][0]=x;
        ds[siz][1]=1;
        siz++;
    }
}
int moder(int t){
    return t<0?t+phi:t;
}
int dfs(int id,int d,int T){
    if(id==siz){
        return 1ll*lg[d]*moder(mu[T/d])%phi;
    }
    int res=0;
    int base=1;
    for(int i=0;i<=ds[id][1];i++){
        res+=dfs(id+1,d*base,T);
        if(res>=phi) res-=phi;
        base*=ds[id][0];
    }
    return res;
}
int alpha(int T){
    breakdown(T);
    return 1ll*dfs(0,1,T)*(n/T)%phi*(n/T)%phi;
}
bool __ed__;
int main(){
    cerr<<(&__ed__-&__st__)/1024.0/1024.0<<endl;
    euler();
    cin>>n;
    int res=1;
    for(int i=1;i<=n;i++){
        res=1ll*res*i%mod;
    }
    res=fpow(res,2*n);
    int ww=0;
    for(int i=1;i<=n;i++){
        ww+=alpha(i);
        if(ww>=phi)ww-=phi;
    }
    res=1ll*res*fpow(fpow(G,ww),mod-3)%mod;
    printf("%d\n",res);
    cerr<<clock()<<endl;
    return 0;
}

最后以 457ms 的最大时间,30.10MB 的最大空间险通过。

posted @ 2026-03-05 08:42  MZMTab  阅读(3)  评论(0)    收藏  举报