Function [容斥原理]

链接

2020 年百度之星·程序设计大赛 - 初赛一
HDOJ6750
http://acm.hdu.edu.cn/showproblem.php?pid=6750

题意

f ( n ) = ∑ i ∣ n , g c d ( i , n i ) = 1 i f(n)=\sum\limits_{i|n,gcd(i,\frac{n}{i})=1}^{}{i} f(n)=in,gcd(i,in)=1i
S ( n ) = f ( 1 ) + f ( 2 ) + . . . + f ( n ) S(n)=f(1)+f(2)+...+f(n) S(n)=f(1)+f(2)+...+f(n)
n ≤ 1 0 12 , 1 ≤ t e s t ≤ 10 n≤10^{12},1≤test≤10 n10121test10

分析

首先对 S ( n ) S(n) S(n) 进行化简:
S ( n ) = − 1 + ∑ ( i , j ) i + j S(n)=-1+\sum\limits_{(i,j)}^{}{i+j} S(n)=1+(i,j)i+j
其中 i , j i,j i,j满足 1 ≤ i ≤ j ≤ n 1≤i≤j≤n 1ijn i ∗ j ≤ n i*j≤n ijn g c d ( i , j ) = 1 gcd(i,j)=1 gcd(i,j)=1

为什么会有一个 − 1 -1 1呢?因为在 i = 1 , j = 1 i=1,j=1 i=1,j=1 的时候 1 1 1 被计算了两遍
接下来我们可以发现,上面的式子中的 i i i 满足 i ≤ n i≤\sqrt{n} in

i > 1 i>1 i>1,那么 j j j 满足 i < j ≤ n i i<j≤\frac{n}{i} i<jin ,因此对于 n \sqrt{n} n 以内的 i i i
要求出 [ i + 1 , n i ] [i+1,\frac{n}{i}] [i+1,in] 内与 i i i 互质的 j j j 的个数以及总和

由于 g c d ( i , j ) = 1 gcd(i,j)=1 gcd(i,j)=1 等价于 g c d ( i , j − i ) = 1 gcd(i,j-i)=1 gcd(i,ji)=1 ,那么只需要算出
k ∈ [ 1 , n i − i ] k∈[1,\frac{n}{i}-i] k[1,ini] 内与 i i i 互质的 k k k 的个数以及总和,就能算出 j j j 的总和

i i i 进行质因数分解,上述 k k k 的个数以及总和都能用容斥原理算出。

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
template<class T>inline void MAX(T &x,T y){if(y>x)x=y;}
template<class T>inline void MIN(T &x,T y){if(y<x)x=y;}
template<class T>inline void rd(T &x){
    x=0;char o,f=1;
    while(o=getchar(),o<48)if(o==45)f=-f;
    do x=(x<<3)+(x<<1)+(o^48);
    while(o=getchar(),o>47);
    x*=f;
}
const int M=1e6+5;
const int P=1e9+7;
int cas,prime[M/10],ptot,pre[M];
void calc(int *Q,int top,ll n,int &sum,int &cnt){
    for(int i=0;i<(1<<top);i++){
        int cur=0,num=1;
        for(int j=0;j<top;j++)if(i&1<<j)cur++,num*=Q[j];
        int tmp=n/num%P;
        if(cur%2==0)sum=(sum+1ll*tmp*(tmp+1)/2%P*num)%P,cnt=(cnt+tmp)%P;
        else sum=(sum-1ll*tmp*(tmp+1)/2%P*num+P)%P,cnt=(cnt-tmp+P)%P;
    }
}
static int Q[35];
int main(){
#ifndef ONLINE_JUDGE
    freopen("jiedai.in","r",stdin);
//    freopen("jiedai.out","w",stdout);
#endif
    for(int i=2;i<M;i++){
        if(pre[i]==0)pre[i]=prime[++ptot]=i;
        for(int j=1;j<=ptot;j++){
            int t=i*prime[j];
            if(t>=M)break;
            pre[t]=prime[j];
            if(i%prime[j]==0)break;
        }
    }
    rd(cas);
    while(cas--){
        ll n;
        rd(n);
        int ans=(n-1+(n%P)*(n%P+1)/2)%P;
        for(int i=2;1ll*i*i<=n;i++){
            int sum=0,cnt=0,top=0,num=i;
            while(num>1){
                int x=pre[num];
                Q[top++]=x;
                while(num%x==0)num/=x;
            }
            calc(Q,top,n/i-i,sum,cnt);
            ans=(ans+2ll*cnt*i%P+sum)%P;
        }
        printf("%d\n",ans);
    }
    return (0-0);
}

比赛的时候少敲了一个1ll*,交上去居然是MLE,赛后加上就过了,呜呜

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值