题目链接:2018ACM-ICPC沈阳网络赛
题目描述:
题目思路:分解质因子+容斥
首先推出an的公式为n(n+1),由题目的思路我们是要求出1~n的数中与m互质的数的a序列的和,那可以求出an的前n项和为n(n+1)(2n+1)/6+n(n+1)/2,然后找出与小于n与m互质的数之和sum,用前一项减去后一项就是解。
所以将m分解质因数,由质因数推出小于n且与m不互质的数,如找到一个质因子k,则2*k,3*k……n/k*k都是满足条件的不互质的数,共有n/k项,因此这个因子对答案的贡献程度为(k*n)^2+k*n的前n项之和,这个值为:k*k*n*(n+1)(2*n+1)/6+k*n(n+1)
最后,因为一个数有可能会被许多质因子筛选掉,我们要枚举所有情况并用容斥的方法减去重复的值。
注意细节:在很大的数中除运算取模记得取逆元,防止爆long long 。
代码和思路均参考于大佬的博客:(https://blog.csdn.net/Lngxling/article/details/82530798)
果然蒟蒻就算是有了思路也写不好代码T T
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define mod 1000000007
ll prime[10100];
int pl=0;
bool vis[10100];
ll n,m;
/*
分解质因子+容斥
*/
//素数表
void getprime()
{
for(ll i=2;i<10010;i++)
{
if(vis[i]==false)
{
prime[++pl]=i;
}
for(int j=1;j<=pl&&i*prime[j]<10010;j++)
{
vis[i*prime[j]]=true;
if(i%prime[j]==0)
break;
}
}
}
ll num[15];
int tot;
//分解质因子
void bre(ll n)
{
tot=0;
for(int i=1;i<=pl&&prime[i]*prime[i]<=n;i++)
{
if(n%prime[i]==0)
{
num[tot++]=prime[i];
while(n%prime[i]==0)
{
n/=prime[i];
}
}
if(n==1)
break;
}
if(n!=1)
{
num[tot++]=n;
}
}
//取逆元,用到快速幂取模
ll inv2,inv3,inv6;
ll fpow(ll a,ll b)
{
ll ans=1;
ll tmp=a%mod;
while(b)
{
if(b&1)
ans=ans*tmp%mod;
tmp=tmp*tmp%mod;
b/=2;
}
return ans;
}
void solve()
{
ll ans=0;
//取所有组合的情况
for(int i=0;i<(1<<tot);i++)
{
int cnt=0;
ll sum=1;
for(int j=0;j<tot;j++)
{
if(i&(1<<j))
{
cnt++;
sum*=num[j];
}
}
ll k=n/sum;
sum%=mod;
//计算公式,求(sum*n)^2+sum*n的前n项之和
ll p=(1+k)*k%mod*inv2%mod*sum%mod;
ll q=k*(k+1)%mod*(2*k+1)%mod*inv6%mod*sum%mod*sum%mod;
//容斥
if(cnt&1)
{
ans-=p;
if(ans<0)
ans+=mod;
ans-=q;
if(ans<0)
ans+=mod;
}
else
{
ans=(ans+p);
if(ans>mod)
ans-=mod;
ans+=q;
if(ans>mod)
ans-=mod;
}
}
printf("%lld\n",ans);
}
int main()
{
getprime();
//记得取逆元
inv2=fpow(2,mod-2);
inv6=fpow(6,mod-2);
while(scanf("%lld%lld",&n,&m)!=EOF)
{
bre(m);
solve();
}
return 0;
}