NOI模拟 刀客球

涉及知识点:数论,单调栈

题意

给你一串长度为 \(n\ (\leq10^5)\) 的序列,值域 \(\leq 10^7\),求该序列所有区间的 \(\text{lcm}\) 之积,对 \(10^9+7\) 取模。

思路

形如求 “所有区间” 的某种值的这种题目,肯定不能直接直接思考每个区间的贡献,因为但就枚举区间的基本复杂度 \(O(n^2)\) 是很难降下来的,一般这种情况有两种解决方式:

  1. \(O(n)\) 顺序遍历所有点,对于节点 \(i\) 可以在 \(O(logn)\) 或者 \(O(\sqrt{n})\) 的复杂度之内求出以 \(i\) 结尾的所有区间的贡献。
  2. \(O(n)\) 遍历所有节点,并且可以快速计算出节点 \(i\) 决定了哪些节点的贡献。本题采用的是这种做法。

首先考虑如何计算一个区间的 \(\text{lcm}\),直接利用 \(\text{lcm}(a,b,c)=\text{lcm}(\text{lcm}(a,b),c)\) 是不现实的,不同于 \(\gcd\) 会越操作越小,这样计算 \(\text{lcm}\) 会导致非常大的结果。但还有另外一种计算方式,即我们将每个数质因数分解,然后将每个质因数的次方数取区间最大值,最后相乘即可得到区间 \(\text{lcm}\),形式化地说,设 \(ai=p_1^{q_1}p_2^{q_2}\cdots p_k^{q_k}\),那么区间 \(a_l,\dots,a_r\) 的区间 \(\text{lcm}\) 即为 \(\Large {p_1^{\large\max\limits_{i=l\sim r}q_i}p_2^{\large\max\limits_{i=l\sim r}q_i}\cdots \ p_k^{\large\max\limits_{i=l\sim r}q_i}}\)

于是我们想到可以分开计算每个质因数的贡献,对于第 \(i\) 个数 \(a_i\) 的第 \(j\) 个质因数 \(p_{i,j}\),记 \(a_i\) 分解后 \(p_{i,j}\) 上的指数为 \(q_{i,j}\)\(p_{i,j}\) 做出的贡献即为 \(a_i\) 往左往右拓展直到某个数质因数分解后 \(p_{i,j}\) 的指数大于 \(q_{i,j}\),形式化的讲,找到一个极大的区间 \([l,r]\) 使得 \(\forall t\in[l,r],at=p_1^{q_1}p_2^{q_2}\cdots p_k^{q_k}\),如果有 \(p_x=p_{i,j}\),满足 \(q_x\leq q_{i,j}\)(两边都取小于等于可能会导致重复计数,所以在实现时要左闭右开)。

实现

为了常数更优,我们将分解出的质因数分成两部分计算,以 \(\sqrt{\text{值域}}\) 为分界线,大于此分界线的质因数在每个数中最多出现 \(1\) 次,上文所述的”能拓展的区间“很显然就是左边第一个与右边第一个含有相同质因子的数,但是由于统计时”左开右闭“(代码里是左开右闭方便写,左闭右开也行),实际上计数时右端点是序列尾端点;而小于等于分界线的数通过单调栈实现 \(O(n)\)​ 找左右能拓展的最远点。

代码

#include<bits/stdc++.h>
using namespace std;
#ifdef ONLINE_JUDGE
#define getchar __getchar
inline char __getchar(){
    static char ch[1<<20],*l,*r;
    return (l==r&&(r=(l=ch)+fread(ch,1,1<<20,stdin),l==r))?EOF:*l++;
}
#endif
template<class T>inline void rd(T &x){
    T res=0,f=1;
    char ch=getchar();
    while(ch<'0' || ch>'9'){if(ch=='-')f=-1; ch=getchar();}
    while('0'<=ch && ch<='9'){res=res*10+ch-'0';ch=getchar();}
    x=res*f;
}
template<class T>inline void wt(T x,char endch='\0'){
    static char wtbuff[20];
    static int wtptr;
    if(x==0){
        putchar('0');
    }
    else{
        if(x<0){x=-x;putchar('-');}
        wtptr=0;
        while(x){wtbuff[wtptr++]=x%10+'0';x/=10;}
        while(wtptr--) putchar(wtbuff[wtptr]);
    }
    if(endch!='\0') putchar(endch);
}
typedef long long LL;
const LL P=1e9+7;
const int MAXN=100005,MAXA=1e7;
int n,pre[MAXN],aft[MAXN],cnt[MAXN];
LL a[MAXN],ans=1;
inline LL qpow(LL x,LL y){
    x%=P;
    LL res=1;
    while(y){
        if(y&1) res=res*x%P;
        x=x*x%P;
        y>>=1;
    }
    return res;
}
int pri[MAXA],pri_cnt=0;
bool vis[MAXA+5];
inline void get_prime(){
    for(int i=2;i<=MAXA;i++){
        if(!vis[i]) pri[++pri_cnt]=i;
        for(int j=1;j<=pri_cnt;j++){
            if(1LL*pri[j]*i>MAXA) break;
            vis[pri[j]*i]=1;
            if(i%pri[j]==0) break;
        }
    }
}
int stk[MAXN],stp;
pair<int,int>tot[MAXN];
int totcnt=0;
int main(){

    get_prime();

    rd(n);
    for(int i=1;i<=n;i++){
        rd(a[i]);
    }

    for(int j=1;1LL*pri[j]*pri[j]<=MAXA;j++){
        int it=pri[j];
        for(int i=1;i<=n;i++){
            cnt[i]=0;
            while(a[i]%it==0){
                a[i]/=it;
                cnt[i]++;
            }
        }

        stp=1;
        stk[1]=0;
        cnt[0]=0x3f3f3f3f;
        for(int i=1;i<=n;i++){
            while((stp) && (cnt[i]>cnt[stk[stp]])) stp--;
            pre[i]=stk[stp];stk[++stp]=i;
        }

        stp=1;
        stk[1]=n+1;
        cnt[n+1]=0x3f3f3f3f;
        for(int i=n;i>=1;i--){
            while((stp) && (cnt[i]>=cnt[stk[stp]])) stp--;
            aft[i]=stk[stp];stk[++stp]=i;
        }
        for(int i=1;i<=n;i++){
            ans=ans*qpow(it,1LL*cnt[i]*(i-pre[i])*(aft[i]-i))%P;
        }
    }

    for(int i=1;i<=n;i++){
        if(a[i]>1) tot[++totcnt]=make_pair(a[i],i);
    }
    sort(tot+1,tot+1+totcnt);
    for(int l=1,r,lst;l<=totcnt;){
        r=l;
        while(r+1<=totcnt && tot[l].first==tot[r+1].first) r++;
        lst=0;
        for(int i=l;i<=r;i++){
            ans=ans*qpow(tot[i].first,1LL*(tot[i].second-lst)*(n-tot[i].second+1))%P;
            lst=tot[i].second;
        }
        l=r+1;
    }

    wt(ans,'\n');
    return 0;
}

处理小于分界线的数的循环:

枚举质因数 \(O(\sqrt R)\),分解质因数 \(O(\log n)\),单调栈 \(O(n)\),综合最大复杂度 \(O(n\sqrt R)\)\(R\) 为值域

处理大于分界线的数的循环:

双指针,\(O(n)\)

posted @ 2024-05-10 00:16  MessageBoxA  阅读(10)  评论(0编辑  收藏  举报