🌑

小羊儿的心情天空

2019ICPC西安 C. Dirichlet k-th root

Dec 15, 2019 由 小羊

我不会狄利克雷卷积。输得彻底。

给出一个很直观的解法代码。

前提是理解好 f(n)=xf(n) = xfk(n)=kx+bk,nf^k(n) = kx+b_{k,n}

那么

f2k(n)=(fkfk)(n)=2fk(n)+dn,d1,dnfk(d)fk(n/d)f^{2k}(n) = (f^k * f^k)(n) = 2f^k(n) + \sum_{d|n, d\not=1,d\not=n} f^k(d) f^k(n/d)

fk+1(n)=(fkf)(n)=fk(n)+f(n)+dn,d1,dnf(d)fk(n/d)f^{k+1}(n) = (f^k * f)(n) = f^k(n) + f(n) + \sum_{d|n, d\not=1,d\not=n} f(d) f^k(n/d)

首先显然 fk(1)=1f^k(1) = 1,假设已经求出 f(1)f(n1)f(1) \cdots f(n-1) 里的所有值,以及可能取到的上标 kk,那么就可以得到 g(n)=fK(n)=Kx+bK,ng(n) = f^K(n) = Kx+b_{K,n}。我们可以从小往大递推 bi,nb_{i,n},然后求得 bK,nb_{K,n} 后得到 f(n)f(n),并解出 fk(n)f^k(n) 以方便后续计算。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+5;
const int mod = 998244353;
int g[maxn], f[maxn][64], b[maxn][64];
vector<int> d[maxn];
int n, k, kk[64], invk, tok;

int fpow(long long a, int k) {
    long long b = 1;
    for (; k; k >>= 1, a = a * a % mod)
        if (k & 1) b = b * a % mod;
    return b;
}

int main()
{
    scanf("%d %d", &n, &k);
    for (int i = 1; i <= n; i++)
        scanf("%d", &g[i]);
    for (int i = 2; i <= n; i++)
        for (int j = i+i; j <= n; j += i)
            d[j].push_back(i);
    for (int tmp = k; tmp; ) {
        kk[++tok] = tmp;
        if (tmp % 2 == 1) tmp--; else tmp /= 2;
    } // the used ks
    invk = fpow(k, mod-2);

    // find for each f(n)
    for (int i = 1; i <= tok; i++) f[1][i] = 1;
    for (int i = 2; i <= n; i++)
    {
        // if we know about f[1..n-1], let f[n] = x
        // so it can be proved that fk[n] = kx+b
        for (int j = tok-1; j; j--)
        {
            long long tmp = b[i][j+1];
            if (!(kk[j] & 1)) tmp <<= 1;
            int sec = (kk[j] & 1) ? tok : j+1;
            for (auto dd : d[i])
                tmp += 1ll * f[dd][j+1] * f[i/dd][sec] % mod;
            b[i][j] = tmp % mod;
        }

        // now we have g[n] = Kx+b, just...
        f[i][tok] = 1ll * (g[i] - b[i][1] + mod) * invk % mod;
        for (int j = tok-1; j; j--)
            f[i][j] = (1ll * kk[j] * f[i][tok] + b[i][j]) % mod;
        assert(f[i][1] == g[i]);
    }

    for (int i = 1; i <= n; i++)
        printf("%d ", f[i][tok]);
    return 0;
}

复杂度为 O(nlognlogk)O(n\log n \log k)

另外给出一个道听途说的做法,当时完全没有想到的。

如果你手算能力够强,那么可以发现 bb 是个类似于求和的东西,结果对 kk 敏感,如果令 k=998244353k = 998244353,那么求和结果为 00,对 gginvk\operatorname{inv}k 次也可以得到 ff

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+5;
const int mod = 998244353;
int g[maxn], f[maxn], n;

int fpow(long long a, int k) {
    long long b = 1;
    for (; k; k >>= 1, a = a * a % mod)
        if (k & 1) b = b * a % mod;
    return b;
}

void conv(int a[maxn], const int b[maxn]) {
    static int c[maxn];
    fill(c, c+n+1, 0);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j * i <= n; j++)
            c[j*i] = (c[j*i] + 1ll * a[i] * b[j]) % mod;
    copy(c, c+n+1, a);
}

int main()
{
    int k; scanf("%d %d", &n, &k);
    for (int i = 1; i <= n; i++)
        scanf("%d", &g[i]);
    k = fpow(k, mod-2); f[1] = 1;
    for (; k; k >>= 1, conv(g, g))
        if (k & 1) conv(f, g);
    for (int i = 1; i <= n; i++)
        printf("%d ", f[i]);
    return 0;
}

不太会证明,能AC,复杂度为 O(nlognloginvk)O(n\log n \log \operatorname{inv} k)