JZOJ4860【NOIP2016提高A组集训第7场11.4】分解数

本文探讨了一种利用动态规划解决数列中特定区间最小公倍数的质因数分解次数的问题。通过预处理质数及最小质因子,采用双指针技巧,实现了高效的区间查询算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

Dpstr学习了动态规划的技巧以后,对数的分解问题十分感兴趣。
Dpstr用此过程将一个正整数x分解成若干个数的乘积:一开始令集合A中只有一个元素x,每次分解时从A中取一个元素a并找出两个大于1且互质的整数p,q,要求pq=a,然后将a分解成两个元素p和q,也就是从A中删去a并加入p和q。Dpstr把正整数x用该过程能分解的次数的最大值称为x的分解数。
例如66的分解数为2,因为最多分解2次。一种分解过程为:一开始A={66},第1次将66分解为11×6,此时A={11,6},第2次将6分解为2×3,此时A={11,2,3},之后无法分解。还可以知道,11,2,3的分解数均为0,因为它们一开始就无法分解。
不过只分解一个数对Dpstr来说不够有趣。Dpstr生成了一个包含n个正整数的数列a1, a2, …, an,请你回答有多少对正整数(l,r)满足1≤l≤r≤n且lcm(al, al+1, …, ar-1, ar)的分解数恰为k。其中lcm(al, al+1, …, ar-1, ar)表示数列从第l项到第r项的所有数的最小公倍数,特别地,当l=r时,lcm(al)=al。由于答案可能很大,只需输出满足条件的正整数对个数除以10,007的余数。

Data Constraint

对于20%的数据,1≤n≤10,1≤k≤5,1≤ai≤20;
对于40%的数据,1≤n≤100,1≤k≤10,1≤ai≤100;
对于60%的数据,1≤n≤1,000,1≤k≤1,000,1≤ai≤100,000;
对于100%的数据,1≤n≤1,000,000,1≤k≤5,000,000,1≤ai≤10,000,000。

Solution

这道题实质就是求有多少个区间的lcm的分解质因数后的个数恰为m+1。由于对于一个右端点,它的左端点的答案是有二分性的,所以我们考虑用指针。对于一个右端点k,我们设左端点的两个指针分别为i,j,表示左端点在[i,j]内的答案为m+1。同时我们打两个桶,记录[i,k]和[j,k]的lcm有多少质因子,动态维护一下使[i,j]始终合法即可。

现在我们再来考虑一下怎样预处理。我们先用欧拉筛处理处10,000,000内的质数,同时处理处每个数的最小质因子。对于每个输入的数,我们不断除以这个数的最小质因数。所以复杂度为O(N*logN)。

Code

#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
const int maxn1=10000000,maxn2=1000000;
int g[maxn2],g1[maxn2];
int b[maxn2][9],d[maxn2],xiao[maxn1];
int n,i,t,j,k,l,m,sum,x,ans,sum1;
bool bz[maxn1+5],bz1;
int main(){
    freopen("dec.in","r",stdin);freopen("dec.out","w",stdout);
//  freopen("data.in","r",stdin);
    bz[1]=true;
    for (i=2;i<=maxn1;i++){
        if (!bz[i]) d[++d[0]]=i,xiao[i]=d[0];
        for (j=1;j<=d[0];j++){
            if (i*d[j]>maxn1) break;
            bz[i*d[j]]=true;
            xiao[i*d[j]]=min(j,xiao[i]);
            if (!(i%d[j])) break;
        }
    }
    scanf("%d%d",&n,&m);
    m++;
    for (i=1;i<=n;i++){
        scanf("%d",&t);
        while (t>1){
            k=xiao[t];
            b[i][++b[i][0]]=xiao[t];
            while (!(t%d[k]))
                t/=d[k];
        }
    }
    i=1;j=1;
    for (k=1;k<=n;k++){
        t=0;
        for (l=1;l<=b[k][0];l++){
            if (!g[b[k][l]]) sum++;
            g[b[k][l]]++;
            if (!g1[b[k][l]]) sum1++;
            g1[b[k][l]]++;
        }
        if (sum>=m){
            while (sum>m){
                for (l=1;l<=b[i][0];l++){
                    g[b[i][l]]--;
                    if (!g[b[i][l]]) sum--;
                }
                i++;
            }
            if (sum==m){
                bz1=true;
                while (1){
                    for (l=1;l<=b[j][0];l++){
                        g1[b[j][l]]--;
                        if (!g1[b[j][l]]) sum1--;
                    }
                    if (sum1<m){
                        for (l=1;l<=b[j][0];l++){
                            if (!g1[b[j][l]]) sum1++;
                            g1[b[j][l]]++;
                        }
                        break;
                    }
                    j++;
                }       
                if (j>=i) ans+=j-i+1;
            }
            ans=ans%10007;
        }else sum+=t;
    }
    printf("%d\n",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值