bzoj DZY Loves Math V
Submit: 509 Solved: 284
[Submit][Status][Discuss]
Description
给定n个正整数a1,a2,…,an,求
的值(答案模10^9+7)。
Input
第一行一个正整数n。
接下来n行,每行一个正整数,分别为a1,a2,…,an。
Output
仅一行答案。
Sample Input
3
6
10
15
6
10
15
Sample Output
1595
HINT
1<=n<=10^5,1<=ai<=10^7。共3组数据。
Source
还是喜欢数论题hhhh,至少不用想那么久。。。。
首先φ是积性函数,这就提示我们可以质因数分解。
我们只要把这个函数在每个质因子下的答案算出来然后都乘起来就行了。
于是现在的问题就变成了当a[1],a[2],,,,a[n]在质数p上的次数分别为 b[1],b[2],,,,b[n]时我们如何求出答案。
我一开始想了个比较zz的做法,暴力大背包,,,,dp[i]表示指数和为i的有多少种方法凑到,然后这个质因子的答案
就是1+dp[1]*(p-1)+dp[2]*p*(p-1).......
虽然我毒奶一口随机数据的话这个肯定是能过的,,,,然而出题人不可能这么良心的。。。。
一旦有某个质因子在所有数中出现的次数和很大很大那么我这个算法就gg了。。。
只能另寻方法。。。
最初始的计算某个质因子下答案的式子是:ans=∑φ(p^(c[1]+c[2]+...c[n])) ,其中0<=c[i]<=b[i]。
然后发现这个也是可以像分解质因数一样合成括号的。
上式=1+ [(p-1)/p]*(π(1+p+p^2+,,,+p^b[i])-1)
注意只有当x==0时p^x不用乘(p-1)/p得到φ(p^x)。
#include<bits/stdc++.h> #define ll long long #define maxn 100005 #define maxm 10000000 #define ha 1000000007 #define pb push_back using namespace std; vector<int> g[maxn*20]; //g[i]存第i个出现的质因子的指数集合 int cnt=0,n,m,dy[maxm+5]; int ans=1,now,mx[maxn*20]; int mul[maxn]; inline int ksm(int x,int y){ int an=1; for(;y;y>>=1,x=x*(ll)x%ha) if(y&1) an=an*(ll)x%ha; return an; } inline void dvd(){ //质因数分解预处理出每个质因子有的指数集合 //dy[i]表示i这个质因子是第几个出现的 m=sqrt(now+0.5); int c; for(int i=2;i<=m;i++) if(!(now%i)){ c=0; if(!dy[i]) dy[i]=++cnt; while(!(now%i)) c++,now/=i; g[dy[i]].pb(c),mx[dy[i]]=max(mx[dy[i]],c); if(now==1) break; } if(now!=1){ if(!dy[now]) dy[now]=++cnt; g[dy[now]].pb(1),mx[dy[now]]=max(mx[dy[now]],1); } } inline void solve(int x){ int tot=1,dig=1,pos=dy[x]; mul[0]=1; //预处理等比数列前缀和 for(int i=1;i<=mx[pos];i++){ dig=dig*(ll)x%ha; mul[i]=mul[i-1]+dig; if(mul[i]>=ha) mul[i]-=ha; } for(int i=g[pos].size()-1;i>=0;i--){ tot=tot*(ll)mul[g[pos][i]]%ha; } tot--; if(tot<0) tot+=ha; tot=tot*(ll)(x-1)%ha*(ll)ksm(x,ha-2)%ha; tot++; ans=ans*(ll)tot%ha; } int main(){ // freopen("data.in","r",stdin); // freopen("data.out","w",stdout); scanf("%d",&n); while(n--){ scanf("%d",&now); dvd(); } for(int i=2;i<=maxm;i++) if(dy[i]) solve(i); printf("%d\n",ans); return 0; }
我爱学习,学习使我快乐