2019 11/12 div2 卡常大赛 T2 数学 题解

题目大意

扎实的数学功底是每个信息学竞赛选手必须具备的基本素养之一。

比如计算一个正整数m有多少个正约数,通常的做法是先将分解质因数,将每个质因数的幂次加一之后乘起来。例如60=2² * 3 * 5 ,质因数的幂次分别为2 1 1,因此 60 有(2+1)(1+1)(1+1)=12 个正约数。

现在 nodgd 给你一个正整数序列a1,a2…an,并进行很多次询问:序列前k个数的乘积有多少个正约数的所有质因数都不超过p呢?特别的,1没有质因数,所以无论怎么询问1总是符合条件。

第一行两个整数 n , q ,表示序列的长度和询问的次数。

第二行 n 个正整数 a1,a2,a3…an。

从第三行起的连续q行,每行两个正整数 k , p,表示一次询问。

对于每次询问,输出答案后 mod 998244353 的值。

样例数据
input:
5 6
3 4 5 6 7
1 3
2 3
2 2
4 4
4 5
5 7

output:
2
6
3
12
24
48

思路

首先使用欧拉筛选质数法将所有质数打个标记(同时将每个合数是被哪一个质数筛选掉的用数组which存一下),而这个which数组记录的就是每个合数可以整除的最小的质数,之后在分解质因数时会用到。
由于数据很大,所以我们采用树状数组记录每个合数可以分解出的质因数的指数。
之后分解质因数即可,每次将可以分解的最小的质数分解完,若现在可分解的最小的质数变化时,更新树状数组就ok啦!
注意将提问离线。

附代码

#include<stdio.h> 
#include<bits/stdc++.h> 
#define maxn 100005 
#define int long long 
const int mod = 998244353; 
char buf[1<<20],*p1,*p2; 
#define GC (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?0:*p1++) 
inline int read(){char t=GC;int x=0;while(!isdigit(t)) t=GC;while(isdigit(t)) x=x*10+t-48,t=GC;return x;} 
using namespace std; 
struct data{int id,k,p;}question[maxn]; 
bool cmp(data a,data b){if(a.k==b.k) return a.p<b.p;return a.k<b.k;}
int n,Q,cnt,which[maxn],prime[10005]; 
bool vis[maxn]; 
int pow_mod(int a,int i){int res=1;a=a%mod;while(i){if(i&1) res=res*a%mod;a=a*a%mod;i>>=1;}return res;}
void check_prime(){for(int i=2;i<=maxn;i++){if(!vis[i]){prime[++cnt]=i;which[i]=i;}for(int j=1;i*prime[j]<=maxn&&j<=cnt;j++){vis[i*prime[j]]=true;which[i*prime[j]]=prime[j];if(i%prime[j]==0) break;}}}
int inv[maxn],c[maxn]; 
void get_inv(){inv[1]=1;for(int i=2;i<=maxn;i++){inv[i]=inv[mod%i]*(mod-mod/i)%mod;}for(int i=1;i<=maxn;i++){c[i]=1;}} 
int Inv(int a){return pow_mod(a,mod-2);}
void update(int x,int v){for(int i=x;i<=maxn;i+=i&-i){c[i]=c[i]*v%mod;}return;} 
int get_ans(int x){int res=1;for(int i=x;i>0;i-=i&-i){res=res*c[i]%mod;}return res;} 
int tot[maxn]; 
void fj(int x){ 
    int num=0,can_fj=which[x]; 
    while(x){ 
        if (can_fj!=which[x]){ 
            int temp=tot[can_fj]+num+1; 
            if(tot[can_fj]>=maxn) temp=temp*Inv(tot[can_fj]+1)%mod;
            else if(tot[can_fj]) temp=temp*inv[tot[can_fj]+1]%mod;
            update(can_fj,temp);             
            tot[can_fj]+=num; 
            num=0; 
            can_fj=which[x]; 
        } 
        if(x==1) return; 
        num++,x/=which[x]; 
    } 
    return; 
} 
int a[maxn],Ans[maxn]; 
main(){     
    n=read(),Q=read(); 
    check_prime(); 
    get_inv(); 
    for(int i=1;i<=n;i++){a[i]=read();} 
    for(int i=1;i<=Q;i++){question[i].k=read(),question[i].p=read(),question[i].id=i;}
    sort(question+1,question+1+Q,cmp); 
    int i=1,j=0; 
    while(i<=Q){ 
        while(j<question[i].k){fj(a[++j]);} 
        while(j==question[i].k){Ans[question[i].id]=get_ans(question[i].p),i++;}
    } 
    for(int i=1;i<=Q;i++){printf("%lld\n",Ans[i]);} 
    return 0; 
    //By--oopst
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值