[noip测试]分解数(线性筛+乱搞)

78 篇文章 0 订阅
7 篇文章 0 订阅

题目描述

时间限制:1s 内存限制 128MB
(dec.cpp/c/pas)
【问题描述】
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的余数。

【输入格式】
输入文件名为dec.in。
第一行包含两个正整数n,k,意义如题目所示。
第二行包含n个用空格隔开的正整数,分别表示a1, a2, …, an。

【输出格式】
输出文件名为dec.out。
输出一行一个整数,表示满足条件的正整数对个数除以10,007的余数。

【输入输出样例1】
dec.in dec.out
3 1
2 3 6 4
见选手目录下的dec/dec1.in和dec/dec1.ans。

【输入输出样例1说明】
考虑所有满足1≤l≤r≤4的正整数对(l,r):
1、对于(1,1),lcm(2)=2,分解数为0;
2、对于(2,2),lcm(3)=3,分解数为0;
3、对于(3,3),lcm(6)=6,分解数为1;
4、对于(1,2),lcm(2,3)=6,分解数为1;
5、对于(2,3),lcm(3,6)=6,分解数为1;
6、对于(1,3),lcm(2,3,6)=6,分解数为1。
其中,6的分解数为1是因为{6}可以分解为{2,3},之后无法分解。
因此共有3对正整数(l,r)满足条件。

【输入输出样例2】
dec.in dec.out
10 2
2 3 2 6 15 5 5 5 2 3 29
见选手目录下的dec/dec2.in和dec/dec2.ans。

【输入输出样例3】
见选手目录下的dec/dec3.in和dec/dec3.ans。

【数据规模与约定】
对于20%的数据,1≤n≤10,0≤k≤5,1≤ai≤20;
对于40%的数据,1≤n≤100,0≤k≤10,1≤ai≤100;
对于60%的数据,1≤n≤1,000,0≤k≤1,000,1≤ai≤100,000;
对于100%的数据,1≤n≤1,000,000,0≤k≤5,000,000,1≤ai≤10,000,000。

题解

非常好的一道题,个人认为是本场测试最有价值的一道题。
分析题目可以发现,题目实际上是要求有多少个区间,区间内所有的数共有k个不同的质因数。
f(l,r) 表示 lcm(al,al+1...ar) 的分解数,显然 f(l,r)<=f(l,r+1)
可以考虑先求 f(l,r)>=k 的区间数量 ,然后再求 f(l,r)>k 的区间数量,然后两个答案相减。
之所以这样做是为了保证 l 随着r单调。即如果有 l,r ,并且区间 [1...l,r] 都满足 >=k >k 的条件,当 r 向后移时,要想找到最多的满足条件的区间,l一定也是向后移动的。
我们可以按顺序加入 r ,然后每一次移动l,每加进去一个数或减去一个数就暴力分解质因数,动态维护区间内的质因数个数,然后再统计答案。
线性筛是 O(n) 的,暴力分解质因数是 O(loga) 的,每一个点最多被加或减4次,所以总的时间复杂度是 O(n+4nloga)0.94s ,实际上时间和空间都是很卡的,所以也考察压常数和压内存的能力。

代码

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define P 10000000
#define N 1000000
#define Mod 10007

int n,k,Max,lp,lq,cntp,cntq,ans1,ans2,ans;
int a[N+1],prime[670000],pr[P+1],p[P+1],q[P+1];

inline int read()
{
    int x=0;char ch=getchar();
    while (ch<'0'||ch>'9') ch=getchar();
    while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x;
}
inline void get_p()
{
    for (int i=2;i<=Max;++i)
    {
        if (!p[i]) prime[++prime[0]]=i,pr[i]=i;
        for (int j=1;j<=prime[0]&&i*prime[j]<=Max;++j)
            p[i*prime[j]]=1,pr[i*prime[j]]=prime[j];
    }
}
inline void add(int x)
{
    while (x>1&&!(x%pr[x]))
    {
        if (!p[pr[x]]) ++cntp;
        if (!q[pr[x]]) ++cntq;
        ++p[pr[x]];
        ++q[pr[x]];
        x/=pr[x];
    }
}
inline void addp(int x)
{
    while (x>1&&!(x%pr[x]))
    {
        if (!p[pr[x]]) ++cntp;
        ++p[pr[x]];
        x/=pr[x];
    }
}
inline void minup(int x)
{
    while (x>1&&!(x%pr[x]))
    {
        --p[pr[x]];
        if (!p[pr[x]]) --cntp;
        x/=pr[x];
    }
}
inline void addq(int x)
{
    while (x>1&&!(x%pr[x]))
    {
        if (!q[pr[x]]) ++cntq;
        ++q[pr[x]];
        x/=pr[x];
    }
}
inline void minuq(int x)
{
    while (x>1&&!(x%pr[x]))
    {
        --q[pr[x]];
        if (!q[pr[x]]) --cntq;
        x/=pr[x];
    }
}
int main()
{
    freopen("dec.in","r",stdin);
    freopen("dec.out","w",stdout);
    n=read();k=read();
    for (int i=1;i<=n;++i)
    {
        a[i]=read();
        if (Max<a[i]) Max=a[i];
    }
    get_p();

    lp=1;lq=1;memset(p,0,sizeof(p));cntp=0;cntq=0;
    for (int r=1;r<=n;++r)
    {
        add(a[r]);
        while (1)
        {
            if (lp>r) break;
            if ((!k)?(cntp>=k||cntp-1>=k):(cntp-1>=k))
            {
                minup(a[lp]);
                ++lp;
            }
            else break;
        }
        ans1=(ans1+lp-1)%Mod;
        while (1)
        {
            if (lq>r) break;
            if (cntq-1>k)
            {
                minuq(a[lq]);
                ++lq;
            }
            else break;
        }
        ans2=(ans2+lq-1)%Mod;
    }
    ans=(((ans1-ans2)%Mod)+Mod)%Mod;
    printf("%d\n",ans);
}

总结

①求=的时候可以转化成>=和>来做,这种思路以后要留心。
②有单调性的问题要想到用指针。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值