線性求 $i^i$ 的做法


線性求 \(i^i\) 的做法

方便起見,我們記 \(f_i=i^i\)\(i\) 的最小質因子為 \(p=\mathrm{minp}(i)\),第 \(i\) 個質數為 \(\mathrm{pr}_i\)

對於質數 \(p\) 用快速冪計算,這里復雜度 \(\mathcal O(\frac{n}{\ln n}\log n)\)

對於合數 \(i=pq\)\(f_i=(pq)^{pq}=f_p^qf_q^p\),由於 \(p\le \sqrt{n}\),因此我們可以 BSGS 預處理 \(f_p^{1\cdots B}\) 以及 \((f_p^B)^{1\cdots B}\)

考慮如何快速計算后半部分。回顧線性篩的流程,\(i\) 是在外層枚舉到 \(q\),內層枚舉到 \(p\) 時計算,因此對於 \(q\) 而言,它計算的東西依次為 \(f_q^{\mathrm{pr}_1,\mathrm{pr}_2,\cdots}\),指數增量是 prime gap,即 \(\mathcal O(\ln n)\),因此可以預處理出 \(f_q^{1,2,\cdots,\ln \frac{n}{q}}\),這里復雜度 \(\mathcal O(\frac{n/q}{\ln (n/q)}+\ln \frac{n}{q})\),累加起來 \(\mathcal O(n)\)(實測中,由於枚舉到 \(p=\mathrm{minp}(q)\) 就會 break,所以常數極小)。

上面這個 \(1,2,\cdots,\ln \frac{n}{q}\) 也可以用 BSGS 優化到 \(\sqrt{\ln \frac{n}{q}}\),不過沒啥影響,說不定還跑不過直接暴力。​

時間復雜度 \(\mathcal O(n)\)


下面是一些實驗性代碼:

\(\mathcal O(n)\) 的實現

const int N = 100000005;
const int SN = ((int)sqrt(N) + 5);
const int mod = 998244353;

int qpow(int a, int b) {
    int res = 1;
    while (b > 0) {
        if (b & 1) res = 1ull * res * a % mod;
        a = 1ull * a * a % mod, b >>= 1;
    }
    return res;
}

int bsgs1[SN][SN], bsgs2[SN][SN];
bool vis[N];
int f[N], pr[N / 10], len;
int powers[250], S;

void sieve(int n) {
    f[1] = 1;
    const int B = sqrt(n);
    for (int i = 2; i <= n; i++) {
        if (!vis[i]) {
            pr[++len] = i;
            f[i] = qpow(i, i);
            if (i <= B) {
                bsgs1[i][0] = 1;
                for (int j = 1; j <= B; j++)
                    bsgs1[i][j] = 1ull * bsgs1[i][j - 1] * f[i] % mod;
                bsgs2[i][0] = 1;
                for (int j = 1; j <= B; j++)
                    bsgs2[i][j] = 1ull * bsgs2[i][j - 1] * bsgs1[i][B] % mod;
            }
        }
        powers[0] = 1;
        int cur = 1, gap = 0;
        for (int j = 1; j <= len && i * pr[j] <= n; j++) {
            vis[pr[j] * i] = 1;
            int num = i * pr[j], now = pr[j] - pr[j - 1];
            if (now > gap) {
                S++;
                for (int ex = gap + 1; ex <= now; ex++)
                    powers[ex] = 1ull * powers[ex - 1] * f[i] % mod;
                gap = now;
            }
            cur = 1ull * cur * powers[now] % mod;
            f[num] = 1ull * bsgs1[pr[j]][i % B] * bsgs2[pr[j]][i / B] % mod * cur % mod;
            if (i % pr[j] == 0) break;
        }
    }
    fprintf(stderr, "S = %d\n", S);
    fprintf(stderr, "time used = %.10f\n", (clock()) / 1. / CLOCKS_PER_SEC);
}

實驗數據:

  • \(n=10^7\)\(\text{0.188s}\)
  • \(n=10^8\)\(\text{1.816s}\)

\(\mathcal O(n \log \ln n)\) 的實現(即 prime gap 每次暴力快速冪計算):

const int N = 100000005;
const int SN = ((int)sqrt(N) + 5);
const int mod = 998244353;

int qpow(int a, int b) {
    int res = 1;
    while (b > 0) {
        if (b & 1) res = 1ull * res * a % mod;
        a = 1ull * a * a % mod, b >>= 1;
    }
    return res;
}

int bsgs1[SN][SN], bsgs2[SN][SN];
bool vis[N];
int f[N], pr[N / 10], len;

void sieve(int n) {
    f[1] = 1;
    const int B = sqrt(n);
    for (int i = 2; i <= n; i++) {
        if (!vis[i]) {
            pr[++len] = i;
            f[i] = qpow(i, i);
            if (i <= B) {
                bsgs1[i][0] = 1;
                for (int j = 1; j <= B; j++)
                    bsgs1[i][j] = 1ull * bsgs1[i][j - 1] * f[i] % mod;
                bsgs2[i][0] = 1;
                for (int j = 1; j <= B; j++)
                    bsgs2[i][j] = 1ull * bsgs2[i][j - 1] * bsgs1[i][B] % mod;
            }
        }
        int cur = 1;
        for (int j = 1; j <= len && i * pr[j] <= n; j++) {
            vis[pr[j] * i] = 1;
            int num = i * pr[j];
            cur = 1ull * cur * qpow(f[i], pr[j] - pr[j - 1]) % mod;
            f[num] = 1ull * bsgs1[pr[j]][i % B] * bsgs2[pr[j]][i / B] % mod * cur % mod;
            if (i % pr[j] == 0) break;
        }
    }
    fprintf(stderr, "time used = %.10f\n", (clock()) / 1. / CLOCKS_PER_SEC);
}

實驗數據:

  • \(n=10^7\)\(\text{0.206s}\)
  • \(n=10^8\)\(\text{2.094s}\)

\(\mathcal O(n\log n)\) 的實現(每次暴力計算):

const int N = 100000005;
const int mod = 998244353;

int qpow(int a, int b) {
    int res = 1;
    while (b > 0) {
        if (b & 1) res = 1ull * res * a % mod;
        a = 1ull * a * a % mod, b >>= 1;
    }
    return res;
}

int f[N];

void sieve(int n) {
    f[1] = 1;
    for (int i = 2; i <= n; i++) f[i] = qpow(i, i);
    fprintf(stderr, "time used = %.10f\n", (clock()) / 1. / CLOCKS_PER_SEC);
}

實驗數據:

  • \(n=10^7\)\(\text{0.801s}\)
  • \(n=10^8\)\(\text{8.547s}\)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM