Grisaia (推公式) 2018 第十屆四川省程序設計競賽

https://www.oj.swust.edu.cn/problem/show/2810

 

題意:很簡單就是求題目上面那個式子的和。

做法:直接開始推公式。

ans=\sum_{i=1}^{n}\sum_{j=1}^{i}(n\ mod (i \times j))=\sum_{i=1}^{n}\sum_{j=1}^{i}(n-\left \lfloor \frac{n}{ij} \right \rfloor \times ij)

然後,將前後兩部分分開

ans=\sum_{i=1}^{n}\sum_{j=1}^{i}n-\sum_{i=1}^{n}\sum_{j=1}^{i}\left \lfloor \frac{n}{ij} \right \rfloor \times ij

然後對於前面的一部分直接可以由公式計算得到:\sum_{i=1}^{n}\sum_{j=1}^{i}n=\frac{n^{2}(n+1)}{2}

我們現在考慮後面一部分怎麼計算。我剛剛拿到的時候一點頭緒都沒有。

但不過我們做題做多了,經常可以發現這種式子一般可以化成,兩個上界爲n的和式:

last1=\frac{1}{2}\sum_{i=1}^{n}\sum_{j=1}^{n}\left \lfloor \frac{n}{ij} \right \rfloor \times ij

這樣是對的嗎,很可惜,我打了一下表發現並不對。。。。。

發現是漏掉一些情況,其實我們可以這樣想一個對稱矩陣,你現在想用一個矩陣的所有元素的值,加起來求下三角的值

這是你會發現對角線的值會有問題,這時你會把對角線的值乘上2,在平方。自己yy一下吧。

正確公式這樣的\frac{1}{2}(\sum_{i}^{n}\left \lfloor \frac{n}{i^{2}} \right \rfloor \times i^{2}+\sum_{i=1}^{n}\sum_{j=1}^{n}\left \lfloor \frac{n}{ij} \right \rfloor \times ij)

其實我是打表結合瞎猜的

關於前面一部分可以直接O(\sqrt n),求出來。

我們重點討論後面一部分怎麼做,打了一下表發現根本沒有規律可循,好像也不可以莫比烏斯和歐拉。。。

還是首先先進行變形看看吧(其實是亂搞):

\sum_{i=1}^{n}\sum_{j=1}^{n}\left \lfloor \frac{n}{ij} \right \rfloor \times ij=\sum_{i=1}^{n}\sum_{j=1}^{\left \lfloor \frac{n}{i} \right \rfloor} \left \lfloor \frac{\left \lfloor \frac{n}{i} \right \rfloor}{j} \right \rfloor \times ij我覺得這一步有一點點不好想到,弄了好久,這樣i和j就可以分開。

我們令:h(x)=\sum_{i=1}^{x} \left \lfloor \frac{x}{i} \right \rfloor \times i

然後發現原式可以這樣變化:

\sum_{i=1}^{n}\sum_{j=1}^{\left \lfloor \frac{n}{i} \right \rfloor} \left \lfloor \frac{\left \lfloor \frac{n}{i} \right \rfloor}{j} \right \rfloor \times ij=\sum_{i=1}^{n}i\times h(\left \lfloor \frac{n}{i} \right \rfloor)

這樣以及很不錯了以及可以分塊求這一部分了,但是,如果直接這樣前面的i雖然可以用公式,而後面的則是O(\sum \sqrt {\left \lfloor \frac{n}{i} \right \rfloor})

這個複雜度,雖然到後面會慢慢的降低,但不過前面的複雜的會很大,已經T了。

然後我們觀察i\times h(\left \lfloor \frac{n}{i} \right \rfloor),或者h(x),好像不能篩吧。。。看看有沒有什麼性質,比如說什麼積性函數啊,用杜教篩等等。很遺憾打了一天表毛關係都沒有找到。

我們還是來進行一些稍微嚴格(亂搞)的推導吧:

我們令:g(n)=h(n)-h(n-1),我們發現g(n)的前綴和就是h(n)

g(n)=\sum_{i=1}^{n}\left \lfloor \frac{n}{i} \right \rfloor \times i - \sum_{i=1}^{n-1}\left \lfloor \frac{n-1}{i} \right \rfloor \times i=\sum_{i=1}^{n}(\left \lfloor \frac{n}{i} \right \rfloor -\left \lfloor \frac{n-1}{i} \right \rfloor) \times i這個式子除了i等於n的約數情況下其他全部爲零。

並且等於約數情況下,就等於i 所以就是個約數和:g(n)=\sum_{d|n}d

不信?你可以打打表。

這肯定是一個積性函數,然後我們就可以線性篩了。預處理,他的前面的一部分的和。這樣時間複雜度就可以得到優化了。

 

其實,我真正的經歷是這樣的:確實弄了好久發現h(n)的差是一個積性函數,但不過並不知道是約數和,然後就在哪裏線性篩。

篩了大半天,才篩出來和打表一樣,然後想着進行狄利克雷卷積,用杜教篩,然後捲了大半天,終於才發現就是一個約數和。

然後就百度一下,沒有找到。然後我尋思這一想,直接對剩下的h(n)進行分塊暴力搞,這樣複雜度也不高吧,好像和杜教篩差不多耶。

我記住了約數和的前綴和可以這樣求了。。。

我就接着上面那樣隨便搞了一下就過了。。。。

我們來分析一下複雜度(瞎說)O(1+\sqrt{n} + p+q\sqrt{\left \lfloor \frac{n}{i} \right \rfloor})

其實後面一部分的p是預處理的大小,q也是和p有關係是這樣的\frac{n}{p},我們近似處理一下:

O(p+\frac{n^{3/2}}{p}),我們根據均值不等式,得出了是n的0.75次方差不多最合適,但不過由於內存,加上答案會爆long long 我們用int128來處理,會爆內存,因此我們開2000000萬差不多了。

題解貌似是n^3/2...

#include "bits/stdc++.h"

using namespace std;
///typedef long long ll;
typedef __int128 ll;
const int N = 20000000 + 10;
int vis[N], cnt = 0;
ll f[N], h[N];
long long n, pri[N], num[N];

void print(__int128 x) {
    if (!x) {
        puts("0");
        return;
    }
    string ret = "";
    while (x) {
        ret += x % 10 + '0';
        x /= 10;
    }
    reverse(ret.begin(), ret.end());
    cout << ret << endl;
}

void init() {
    f[1] = vis[1] = 1;
    for (int i = 2; i < N; i++) {
        if (!vis[i]) {
            pri[++cnt] = i;
            f[i] = i + 1;
            num[i] = i;
        }
        for (int j = 1; j <= cnt && i * pri[j] < N; j++) {
            vis[i * pri[j]] = 1;
            if (i % pri[j] == 0) {
                if (i / num[i] != 1) f[i * pri[j]] = f[num[i] * pri[j]] * f[i / num[i]];
                else f[i * pri[j]] = 1LL * i * pri[j] + f[i];
                num[i * pri[j]] = num[i] * pri[j];
                break;
            }
            f[i * pri[j]] = f[i] * f[pri[j]];
            num[i * pri[j]] = pri[j];
        }
    }
    for (int i = 1; i < N; i++) {
        h[i] = h[i - 1] + f[i];
    }
}

ll get_last(ll n) {
    ll ret = 0;
    for (ll i = 1; i * i <= n; i++) {
        ret += (n / (i * i)) * i * i;
    }
    return ret;
}

ll get_s1(ll x) {
    ll ret = x * (x + 1) / 2;
    return ret;
}

ll fun(ll x) {
    if (x < N) return h[x];
    ll ret = 0;
    for (ll l = 1, r; l <= x; l = r + 1) {
        r = x / (x / l);
        ret += (x / l) * (get_s1(r) - get_s1(l - 1));
    }
    return ret;
}

ll solve(ll n) {
    ll ans = n * n * (n + 1) / 2;
    ll pre = 0;
    for (ll l = 1, r; l <= n; l = r + 1) {
        r = n / (n / l);
        pre += (get_s1(r) - get_s1(l - 1)) * fun(n / l);
    }
    ans = ans - (pre + get_last(n)) / 2;
    return ans;
}

int main() {
    int T;
    init();
    scanf("%d", &T);
    while (T--) {
        scanf("%lld", &n);
        print(solve(n));
    }
    return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章