noi.ac#2861

题意

给定长度为 n 的正整数序列 ai,计算 1i,jn(aimodaj)

1n,ai106

思路

观察数据范围,发现算法大概率是 O(nlogn) 的。

先对式子进行转化

1i,jn(aimodaj)=1i,jn(aiai/aj×aj)=1i,jnai1i,jnai/aj×aj

第一项很好处理,考虑如何处理第二项

考虑固定 ai,aj 其中一个,对另一个进行统计。

假如固定 ai ,统计 aj,可以用整除分块做到 O(n1.5)

假如固定 aj,统计 ai,那么我们需要枚举 aj 的倍数,如果 aj 很小的话,单次统计甚至就需要 O(n) 的复杂度,似乎无法保证复杂度。

根号分治?仍然是根号级别的算法,甚至常数远大于整除分块。

诶,每个 aj 的统计复杂度一定要是平均的吗?有没有可能单次复杂度可能很高,但是总复杂度仍为 O(nlogn) 呢?

那么如何处理呢?

解法

发现若是固定 aj,枚举其倍数,那么只有 aj 较小时需要枚举的次数较多,所以考虑对相同的 aj 进行记忆化。

这看似是一个没什么用的优化,但是实际上将算法优化到了 O(nlogn)

为什么捏?

因为总枚举次数是不多于 i=1nni 的,而调和级数是 log 级别的,于是这里算法的复杂度便变成了 O(nlogn)

具体实现上可以对 ai 开桶计数,cnti 即为 iai 中的出现次数,那么每个 i 的贡献为 i×cnti×j=1naji

代码

#include <bits/stdc++.h>

using namespace std;
int n, a[1000005], cnt[1000005], sum[1000005], max_a;
long long ans;
int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) 
        scanf("%d", &a[i]);
    for (int i = 1; i <= n; i++) 
        max_a = max(a[i], max_a);
    for (int i = 1; i <= n; i++) 
        cnt[a[i]]++;
    for (int i = 1; i <= max_a; i++) 
        sum[i] = sum[i - 1] + cnt[i];
    for (int i = 1; i <= max_a; i++) {
        if (!cnt[i]) continue;
        long long cc = 0;
        for (int j = 1; j <= max_a / i; j++) {
            cc += (sum[min(max_a, (j + 1) * i - 1)] - sum[j * i - 1]) * j;
        }
        ans += 1ll * cc * cnt[i] * i;
    } 
    long long total_sum = 0;
    for (int i = 1; i <= n; i++) {
        total_sum += a[i];
    }
    total_sum *= n;
    printf("%lld", total_sum - ans);
    return 0;
}

posted @   wiki0922  阅读(16)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示