CF gym 101615 C

题意

定义F(x) 表示x所有因子的和,让你对一个区间的F(x)求和。
数值的范围有1e12,但是区间长度1e6。

思路

可以严格地推倒,但是以开始得有个切入点。
我的切入点是求F的前缀和,假设记作G。
那么有下面的性质:
G ( 6 ) = F ( 1 ) + F ( 2 ) + F ( 3 ) + F ( 4 ) + F ( 5 ) + F ( 6 ) = ⌊ 6 / 1 ⌋ ∗ 1 + ⌊ 6 / 2 ⌋ ∗ 2 + ⌊ 6 / 3 ⌋ ∗ 3 + ⌊ 6 / 4 ⌋ ∗ 4 + ⌊ 6 / 5 ⌋ ∗ 5 + ⌊ 6 / 6 ⌋ ∗ 6 G(6) = F(1) + F(2) +F(3) + F(4) +F(5) + F(6) \\ = \lfloor 6/ 1 \rfloor * 1 + \lfloor 6 /2 \rfloor * 2 + \lfloor 6 / 3 \rfloor * 3 + \lfloor 6 / 4 \rfloor * 4 + \lfloor 6 / 5 \rfloor * 5 + \lfloor 6/ 6 \rfloor * 6 G(6)=F(1)+F(2)+F(3)+F(4)+F(5)+F(6)=6/11+6/22+6/33+6/44+6/55+6/66
熟悉数论分块的话,可以知道同一块的数字,向下取整是一样的,只要处理一个等差数列求和即可。
稍微爆了一下ll,但是没有溢出ull。

int ans = 0;
for(int l = 1, r = 0; l <= n; l++) {
    r = n / (n / l);
    // do something
}

代码

#include<bits/stdc++.h>

using namespace std;
typedef unsigned long long ll;

ll sigma(ll l, ll r) {
    return (l + r) * (r - l + 1) / 2ull;
}

ll g(ll x) {
    if (x == 0) return 0;
    ll res = 0;

    for (ll l = 1, r = 0; l <= x; l = r + 1) {
        r = x / (x / l);
        res += sigma(l, r) * (x / r);
    }
    
    return res;
}

int main() {
    ll a, b;
    scanf("%llu  %llu", &a, &b);
    ll ans = g(b) - g(a - 1);
    printf("%llu\n", ans);
    return 0;
}

比赛中的代码:

#include<bits/stdc++.h>

using namespace std;
typedef unsigned long long ll;

ll a, b;

ll calcER(ll l, ll r) {
    return (l + r) * (r - l + 1) / 2ll;
}

ll calcPre(ll x) {
    if (x == 0) return 0;

    ll res = 0;
    for (ll i = 0, old; i <= x;) {
        old = i;
        i = x / (x / (i + 1));
//        printf("%lld ", i);
        res += calcER(old + 1, i) * (x / i);
        if(i == x) break;
    }
//    puts("");
    return res;
}

ll scanll() {
    char ch;
    ll res = 0;
    while(isdigit(ch = getchar())){
        res = res * 10 + ch - '0';
    }
    return res;
}

ll stk[105], top;
void printll(ll v){
    while(v) {
        stk[top++] = v % 10;
        v /= 10;
    }
    while(top) {
        printf("%d", int(stk[--top]));
    }
    printf("\n");
}

int main() {
    ll a, b;
//    scanf("%lld  %lld", &a, &b);

    a = scanll();
    b = scanll();

//    printll(calcPre(b));
//    printll(calcPre(a - 1));
    ll ans = calcPre(b) - calcPre(a - 1);
    printll(ans);

    return 0;
}
/*

*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值