數學——數論/雜項

mrsunss發表於2024-04-13

狄利克雷卷積

前置

下取整的性質

$\left\lfloor\frac {\lfloor\frac{a}{b}\rfloor}{c}\right\rfloor=\lfloor \frac{a}{bc} \rfloor$。 證明略。

上取整的性質

$\left\lceil\frac {\lceil\frac{a}{b}\rceil}{c}\right\rceil= \lceil \frac{a}{bc} \rceil$

證明:

$\left\lceil\frac {\lceil\frac{a}{b}\rceil}{c}\right\rceil=\left\lfloor\frac {\lfloor\frac{a+b-1}{b}\rfloor+c-1}{c}\right\rfloor=\left\lfloor\frac {\lfloor\frac{a+b-1+bc-b}{b}\rfloor}{c}\right\rfloor=\left\lfloor\frac {a+bc-1}{bc}\right\rfloor= \lceil \frac{a}{bc} \rceil$

$g=\mu*f$,已知$f$求出$g$

這裡有三種方法

方法1
 for (int i = 1; i <= n; i++) g[i] = 0;
for (int i = 1; i <= n; i++) {
    for (int j = 1; i * j <= n; j++) {
        g[i * j] = (g[i * j] + mu[i] * f[j]) % mod;
    }
}
// 依照定義,O(nlogn)
方法2
 for (int i = 1; i <= N; i++) g[i] = f[i];
for (int i = 1; i <= N; i++) {
    for (int j = 2; i * j <= N; j++) {
        g[i * j] = (g[i * j] - g[i]) % MOD;
    }
}
// 類似求狄利克雷卷積逆的方式,不需要線性篩 mu ,O(nlogn)
方法3
 for (int i = 1; i <= n; i++) g[i] = f[i];
for (auto i : p) {
    for (int j = n / i; j >= 1; j--) {
        g[i * j] = (g[i * j] - g[j]) % MOD;
    }
}//O(nloglogn)

第三種可以理解為$dp$,設$g(i,n)=\sum\limits_{d|n,d只含前i種質因子}f(d)\mu(\frac{n}{d})$

那麼$g(i,n)=\begin{cases} g(i-1,n), \;\;\;p_i\nmid n \\g(i-1,n)-g(i-1,\frac{n}{p_i}),\;\;\;p_i|n \end{cases}$

篩法

埃氏篩

列舉每個數,篩去它們的整數倍。

複雜度$O(nloglogn)$ ,證明略。

線性篩(尤拉篩)

列舉每個數$x$,篩去它們的$p_i$倍($p_i$為質數)。當$x\%p_i=0$時,不需要再往下篩直接$break$,因為$p_i$是遞增的,所以$x$乘上其他的質數的結果一定會被$p_i$的倍數篩掉。顯然這樣每個數只會被篩一次,故複雜度為$O(n)$。

任何積性函式都可以進行線性篩,所有線性篩積性函式都是基於線性篩質數的。

當然完全積性函式也可以線性篩,並且更加容易。不管$i$與$p$是否互質,都有$f(i\cdot p)=f(i) \cdot f(p)$,直接篩就行了。

若想線性篩出積性函式$f(x)$,就必須快速計算出以下函式值,$f(1),f(p),f(p^k)$,其中$p$為質數。

考慮篩的過程中,$i\cdot p$會被$i$乘上$p$給篩掉,將$i$唯一分解得到$p_1^{c_1}p_2^{c_2}\cdots p_k^{c_k}$,其中$p_1\le p_2\le \cdots\le p_k$

則一定有$p\leq p_1$,這是顯然的,因為一旦第一次遇到$i\%p=0$也就是最小質因子時,直接$break$了。

如果$p< p_1$,那麼$p$與$i$互質,可以直接得到$f(i\cdot p)=f(i)f(p)$

如果$p=p1$,這是需要對$i$記錄一個$low_i$,表示$i$中最小質因子的指數次冪,即$low_i=p_1^{c_1}$

如果$i$除掉$low_i$,那麼結果中最小質因子一定大於$p_1$,從而得到$gcd(\frac{i}{low_i},low_i\cdot p)=1$,那麼$f(i\cdot p)=f(\frac{i}{low_i})f(low_i\cdot p)$

舉例

尤拉函式$\varphi$,$\varphi(1)=1$,$\varphi(p)=p-1$。當$i\%p\neq0$時,$i$和$p$互質,$\varphi(i\cdot p)=\varphi(i)\cdot \varphi(p)=\varphi(i)\cdot(p-1)$;當$i\%p=0$時,$\varphi(i\cdot p)=\varphi(\frac{i}{p^c})\varphi(p^{c+1})=\frac{\varphi(i)}{p^c(1-\frac1{p})}p^{c+1}(1-\frac{1}{p})=p\cdot \varphi(i)$

檢視程式碼
 void init(int mod = MOD) {

    np[0] = np[1] = 1;phi[1] = 1;
    for (int i = 2;i < N;i++) {
        if (!np[i]) {
            p.push_back(i);
            phi[i] = i - 1;
        }
        for (auto j : p) {
            if (i * j < N) {
                np[i * j] = 1;
                if (i % j == 0) {
                    phi[i * j] = phi[i] * j;
                    break;
                }
                else {
                    phi[i * j] = phi[i] * (j - 1);
                }
            }
            else {
                break;
            }
        }
    }
}

莫比烏斯函式$\mu$,$\mu(1)=1$,$\mu(p)=-1$。當$i\%p\neq0$時,$i$和$p$互質,$\mu(i\cdot p)=\mu(i)\cdot \mu(p)=-\mu(i)$;當$i\%p=0$時,$\mu(i\cdot p)=\mu(\frac{i}{p^c})\mu(p^{c+1})=\mu(\frac{i}{p^c})\cdot 0=0$

檢視程式碼
 void init(int mod = MOD) {

    np[0] = np[1] = 1;mu[1] = 1;
    for (int i = 2;i < N;i++) {
        if (!np[i]) {
            p.push_back(i);
            mu[i] = -1;
        }
        for (auto j : p) {
            if (i * j < N) {
                np[i * j] = 1;
                if (i % j == 0) {
                    mu[i] = 0;
                    break;
                }
                else {
                    mu[i * j] = -mu[i];
                }
            }
            else {
                break;
            }
        }
    }
}

約數個數函式$d$,$d(1)=1$,$d(p)=2$ 。當$i\%p\neq0$時,$i$和$p$互質,$d(i\cdot p)=d(i)\cdot d(p)=2d(i)$;當$i\%p=0$時,$d(i\cdot p)=d(\frac{i}{p^c})d(p^{c+1})=\frac{d(i)}{c+1}(c+2)$

約數和函式$\sigma$,$\sigma(1)=1$,$\sigma(p)=p+1$,當$i\%p\neq0$時,$i$和$p$互質,$\sigma(i\cdot p)=\sigma(i)\cdot \sigma(p)=(p+1)\sigma(i)$;當$i\%p=0$時,$\sigma(i\cdot p)=\sigma(\frac{i}{p^c})\sigma(p^{c+1})=\frac{\sigma(i)}{\sum\limits_{i=0}^{c}p^i}\sum\limits_{i=0}^{c+1}p^i$

檢視程式碼
 void init(int mod = MOD) {

    np[0] = np[1] = 1;
    sigma[1] = 1;low[1] = 1;
    for (int i = 2;i < N;i++) {
        if (!np[i]) {
            p.push_back(i);
            sigma[i] = i + 1;low[i] = i;
        }
        for (auto j : p) {
            if (i * j < N) {
                np[i * j] = 1;
                if (i % j == 0) {
                    low[i * j] = low[i] * j;
                    if (i == low[i]) sigma[i * j] = sigma[i] * j + 1;
                    else sigma[i * j] = sigma[i / low[i]] * sigma[low[i] * j];
                    break;
                }
                else {
                    low[i * j] = j;
                    sigma[i * j] = sigma[i] * (j + 1);
                }
            }
            else {
                break;
            }
        }
    }
}

下面這個不是積性函式,但是可以用來求解上面的東西,並且也可以線性篩。

最小次冪函式$low$,即$n=p_1^{c_1}p_2^{c_2}...p_k^{c_k}$,$p_1<p_2<...<p_k$,那麼$low(n)=p_1^{c_1}$。

當$i\%p\neq0$時,$i$中最小質因子大於$p$,$low(i\cdot p)=p$;當$i\%p=0$時,$i$中最小質因子為$p$,$low(i\cdot p)=p\cdot low(i)$

杜教篩

模板題:P4213 - 杜教篩

在亞線性時間內求出某些特徵積性函式的字首和

所謂某些特徵積性函式,指的是存在一個對應的積性函式$g$,使得$f*g=h$,且$g$,$h$的字首和可以$O(1)$得知的函式$f$ 。

記$F,G,H$分別為$f,g,h$的字首和,那麼$H(n)=\sum\limits_{i=1}^{n}\sum\limits_{d|n}f(\frac{i}{d})g(d)=\sum\limits_{d=1}^{n}g(d)\sum\limits_{j=1}^{\lfloor \frac{n}{d} \rfloor}f(j)$

$=\sum\limits_{d=1}^{n}g(d)F(\lfloor \frac{n}{d} \rfloor)=g(1)F(n)+\sum\limits_{d=2}^{n}g(d)F(\lfloor \frac{n}{d} \rfloor)$

由於$g$為積性函式,那麼$g(1)=1$,於是就有$F(n)=H(n)-\sum\limits_{d=2}^{n}g(d)F(\lfloor \frac{n}{d} \rfloor)$

注意到,如果從小到大篩的話,右邊的$F$是已知的,而$G,H$都可以$O(1)$求得,則對$\lfloor\frac{n}{d} \rfloor$整除分塊,單論複雜度$O(\sqrt{n})$

事實上,我們一般只關心一個點$n$的$F(n)$,故該式子一般遞迴求解。預處理$n^{\frac{2}{3}}$內的$f$值時,可以取得最優複雜度。

舉例

求$\mu$的字首和,注意到$\mu*1=\varepsilon$,於是有$\sum\limits_{i=1}^{n}\mu(i)=\sum\limits_{i=1}^{n}\varepsilon(i)-\sum\limits_{d=2}^{n}\sum\limits_{i=1}^{\lfloor \frac{n}{d} \rfloor}\mu(i)=1-\sum\limits_{d=2}^{n}\sum\limits_{i=1}^{\lfloor \frac{n}{d} \rfloor}\mu(i)$

求$\varphi$的字首和,注意到$\varphi*1=id$,$\sum\limits_{i=1}^{n}\varphi(i)=\sum\limits_{i=1}^{n}id(i)-\sum\limits_{d=2}^{n}\sum\limits_{i=1}^{\lfloor \frac{n}{d} \rfloor}\varphi(i)=\frac{n(n+1)}{2}-\sum\limits_{d=2}^{n}\sum\limits_{i=1}^{\lfloor \frac{n}{d} \rfloor}\varphi(i)$

求$id\cdot\mu$的字首和,我們需要找到合適的函式$g$,注意到$id*id=\sum\limits_{d|n}d\frac{n}{d}=\sum\limits_{d|n}n=nd(n)$

不妨令$g=id$嘗試一下。於是有$(id\cdot \mu)*id=\sum\limits_{d|n}d\mu(d)\frac{n}{d}=n\sum\limits_{d|n}\mu(d)=n(\mu*1)=n\cdot \varepsilon(n)=\varepsilon(n)$

所以$\sum\limits_{i=1}^{n}id\cdot\mu(i)=\sum\limits_{i=1}^{n}\varepsilon(i)-\sum\limits_{d=2}^{n}\sum\limits_{i=1}^{\lfloor \frac{n}{d} \rfloor}id\cdot\mu(i)=1-\sum\limits_{d=2}^{n}\sum\limits_{i=1}^{\lfloor \frac{n}{d} \rfloor}id\cdot\mu(i)$

求$id\cdot\varphi$的字首和,我們需要找到合適的函式$g$,與上面等同,$g=id$,於是有$(id\cdot \varphi)*id=\sum\limits_{d|n}d\varphi(d)\frac{n}{d}=n\sum\limits_{d|n}\varphi(d)=n(\varphi*1)=n\cdot id(n)=n^2=id_2(n)$

所以$\sum\limits_{i=1}^{n}id\cdot\varphi(i)=\sum\limits_{i=1}^{n}id_2(i)-\sum\limits_{d=2}^{n}\sum\limits_{i=1}^{\lfloor \frac{n}{d} \rfloor}id\cdot\varphi(i)=\frac{n(n+1)(2n+1)}{6}-\sum\limits_{d=2}^{n}\sum\limits_{i=1}^{\lfloor \frac{n}{d} \rfloor}id\cdot\varphi(i)$

杜教篩板子
 int Du_sieve(int n, int pre_f[], map<int, int>& mp) {//f*g=h,求f的字首和
    if (n < N) return pre_f[n];
    if (mp.count(n)) return mp[n];
    int res = n * (n + 1) / 2;//h=id字首和
    for (int l = 2, r;l <= n;l = r + 1) {
        r = n;
        if (n / l) r = min(r, n / (n / l));
        res = (res - Du_sieve(n / l, pre_f, mp) * (r - l + 1));//(r-l+1)為g=1字首和
    }
    return mp[n] = res;
}

min_25篩

$Min25$篩求解字首和本質上是一種$DP$。一般用於求解積性函式字首和。

預設$P$為質數集,$P_i$表示第$i$個質數,預設$p\in P$。

若函式$f(x)$滿足:$f(x)$是積性函式,$f(p)$存在多項式表示或可以快速求值,$f(p^k)$可以快速求值。

核心思想:將$[1,n]$分為質數,合數,$1$三個部分進行討論。

$\sum\limits_{i=1}^{n}f(i)=\sum\limits_{i=1}^{n}[i\in P]f(i)+\sum\limits_{i=1}^{n}[i\notin P]f(i)$

$=\sum\limits_{i=1}^{n}[i\in P]f(i)+f(1)+\sum\limits_{2\le p\le \sqrt n,2\le p^e\le n}f(p^e)\left(\sum\limits_{i=2}^{\lfloor\frac{n}{p^e} \rfloor}f(i)[minp(i)>p]\right)$

首先考慮如何求解$\sum\limits_{i=1}^{n}[i\in P]f(i)$,考慮先線性篩出不大於$\sqrt n$的所有質數作為$P$。記$minp(x)$表示$x$的最小質因子,令$g(n,j)=\sum\limits_{i=1}^{n}[i\in P \; or \; minp(i)>P_j] i^k$,它表示$[1,n]$所有滿足條件的數的$k$次方和,條件是要麼是質數要麼最小質因子大於$P_j$。這裡選取$i^k$是因為它是一個完全積性函式,方便後面的公式推導。

可以發現$g(n,|P|)=\sum\limits_{i=1}^{n}[i\in P]i^k$,即$[1,n]$中所有質數的$k$次方和。為方便不妨令$g(n)=g(n,|P|)$。其中$|P|$表示$P$的大小,也就是不大於$\sqrt n$的質數數量。

考慮如何狀態轉移,$g(n,j)$在$g(n,j-1)$的基礎上少了最小質因子恰好為$P_j$的合數。提出來一個$P_j$作為最小質因子,這樣剩下的數也不能有小於它的質因子。也就是$g(\lfloor\frac{n}{P_j} \rfloor,j-1)-\sum\limits_{i=1}^{j-1}P_i^k$,後面這個是為了去掉前面滿足條件的質數。這樣我們可以得到遞推式:$g(n,j)=g(n,j-1)- P_j^k\left[g(\lfloor\frac{n}{P_j} \rfloor,j-1)-\sum\limits_{i=1}^{j-1}P_i^k\right]$,式子中$\sum\limits_{i=1}^{j-1}P_i^k$,可以在篩素數的時候一併處理。

答案就是先求出所有質數的函式和,然後先列舉一個$p^e$,再列舉最小質因子大於$p$的數。

還是可以考慮$DP$的思想。設$S(n,x)$表示$[1,n]$中所有最小質因子大於$P_x$的函式值$f$之和。答案就是$S(n,0)$。

我們將滿足條件的數分成兩部分,第一部分是大於$P_x$的質數,即$g(n)-\sum\limits_{i=1}^{x}P_i^k$。另一部分則是最小質因子大於$P_x$的合數,列舉最小質因子即可。

於是$S(n,x)=g(n)-\sum\limits_{i=1}^{x}P_i^k+\sum\limits_{p_k^e\le n ,k>x }f(p_k^e)\left(S\left(\lfloor\frac{n}{p_k^e}\rfloor,k\right)+[e\ne1]\right)$

檢視程式碼
 namespace min25 {
        const int SIZE = 1e5 + 10;
        const int EXP_SIZE = 2;
        int primes[SIZE], minFac[SIZE], pfx[SIZE][EXP_SIZE], primesPt, lim;
        ll g[SIZE * EXP_SIZE][EXP_SIZE], dsc[SIZE * EXP_SIZE], inv2, inv3;
        array<int, 2> indx[SIZE];  // indx[x]: index of <x, n / x>
        const array<int, 2> csts[EXP_SIZE] = { {-1, 1}, {1, 2} };

        void initPrimes(int siz) {
            inv2 = qp(2, MOD - 2, MOD); inv3 = qp(3, MOD - 2, MOD);
            fill(minFac + 0, minFac + siz + 1, 0); primesPt = 0;
            for (int i = 2; i <= siz; i++) {
                if (minFac[i] == 0) minFac[i] = i, primes[++primesPt] = i;
                for (int j = 1; j <= primesPt && primes[j] <= min(minFac[i], siz / i); j++) minFac[i * primes[j]] = primes[j];
            }

            //f(p)的多項式每一項的字首和
            for (int i = 1; i <= primesPt; i++) {
                for (int e = 0; e < EXP_SIZE; e++) {//展開寫可能變快
                    pfx[i][e] = Add(pfx[i - 1][e], qp(primes[i], csts[e][1], MOD));
                }
            }
        }

        //計算f(p)
        const auto f = [](ll p) {
            p %= MOD;
            return p * (p - 1) % MOD;
            //ll res = 0;
            // for (int e = 0; e < EXP_SIZE; e++) {//展開寫會快很多
            //     res = (res + csts[e][0] * qp(p, csts[e][1], MOD)) % MOD;
            // }
            // return res;
            };

        //i^k字首和,次數高的話需要拉插
        const auto sum = [](ll n, ll exp) {
            n %= MOD;
            if (exp == 0) return n;
            ll res = n * (n + 1) % MOD * inv2 % MOD;
            if (exp == 2) return res * ((n << 1) + 1) % MOD * inv3 % MOD;
            return res;
            };

        ll sieve(ll x, ll pt, ll n) {
            if (x <= 1 || primes[pt] > x) return 0;
            int k = x <= lim ? indx[x][0] : indx[n / x][1];
            ll res = 0;
            // for (int e = 0; e < EXP_SIZE; e++) {//展開寫會快很多
            //     res = (res + csts[e][0] * (g[k][e] - pfx[pt - 1][e]) % MOD + MOD) % MOD;
            // }
            res = Add(res, Dec(pfx[pt - 1][0], g[k][0]));
            res = Add(res, Dec(g[k][1], pfx[pt - 1][1]));

            for (int i = pt; i <= primesPt && 1ll * primes[i] * primes[i] <= x; i++) {
                ll pk = primes[i], pk1 = 1ll * primes[i] * primes[i];
                for (int e = 1; pk1 <= x; pk = pk1, pk1 *= primes[i], e++) {
                    res = (res + f(pk) * sieve(x / pk, i + 1, n) % MOD + f(pk1)) % MOD;
                }
            }

            return (res + MOD) % MOD;
        }

        ll run(ll n) {
            lim = sqrt(n); initPrimes(lim); int dscPt = 0;
            for (ll l = 1, r; l <= n; l = r + 1) {
                r = n / (n / l); ll v = n / l; dsc[dscPt] = v;
                for (int e = 0; e < EXP_SIZE; e++)
                    g[dscPt][e] = sum(dsc[dscPt], csts[e][1]) - 1;
                v <= lim ? indx[v][0] = dscPt : indx[n / v][1] = dscPt; dscPt++;
            }

            for (int i = 1; i <= primesPt; i++) {
                for (int j = 0; j < dscPt && 1ll * primes[i] * primes[i] <= dsc[j]; j++) {
                    ll v = dsc[j] / primes[i];
                    int k = v <= lim ? indx[v][0] : indx[n / v][1];

                    // for (int e = 0; e < EXP_SIZE; e++) {//展開寫會快很多
                    //     g[j][e] = (g[j][e] - qp(primes[i], csts[e][1], MOD) * (g[k][e] - pfx[i - 1][e] + MOD) % MOD + MOD) % MOD;
                    // }

                    g[j][0] = Dec(g[j][0], mul(primes[i], Dec(g[k][0], pfx[i - 1][0])));
                    g[j][1] = Dec(g[j][1], mul(mul(primes[i], primes[i]), Dec(g[k][1], pfx[i - 1][1])));

                }
            }

            return Add(sieve(n, 1, n), 1);
        }

整除分塊

前置

給定$b,c$ ,求$a$ 。

$a\cdot b \le c$,則有$a\le \lfloor \frac{c}{b} \rfloor$

$a\cdot b < c$,則有$a< \lceil \frac{c}{b} \rceil$ ,也就是$a\cdot b\le c-1$,$a \le \lfloor\frac{c-1}{b} \rfloor$

$a\cdot b \ge c$,則有$a\ge \lceil \frac{c}{b} \rceil$

$a\cdot b > c$,則有$a> \lfloor \frac{c}{b} \rfloor$ ,也就是$a\cdot b\ge c+1$,$a \ge \lceil\frac{c+1}{b} \rceil$

對於$\lfloor\frac{n}{i}\rfloor$類似的式子,對於一個左邊界$l$,其值為$k=\lfloor\frac{n}{l}\rfloor$,右邊界$r$即找滿足$k\le \lfloor\frac{n}{i}\rfloor$的最大的$i$ ,也就是使得$ik\le n$最大的$i$,即$r=\lfloor\frac{n}{k}\rfloor$,即$r= \left\lfloor \frac{n}{\lfloor\frac{n}{l} \rfloor} \right \rfloor$

檢視程式碼
int lim = n;
for (int l = 1, r;l <= lim;l = r + 1) {
    r = lim;
    if (n / l) r = min(r, n / (n / l));
    //操作
}

擴充

我對整除分塊的理解:

首先用$\lfloor\frac{n}{l}\rfloor$得到$k$,然後$\frac{n}{l}$應該為$k.幾$,也就是$k+\varepsilon_1$,其中$\varepsilon_1\in[0,1)$

接下來用$\frac{n}{k}$,如果是$\frac{n}{k+\varepsilon_1}$,就會得到$l$,然而去掉了$\varepsilon_1$,$k$減小了,於是得到的值就變大,得到了$r+\varepsilon_2$。其中$\varepsilon_2\in[0,1)$

於是可以得出的是,在$x\in[l,r+\varepsilon_2]$範圍,如果用$\frac{n}{x}$都將得到$[k,k+\varepsilon_1]$範圍的值,其中$x=r+\varepsilon_2$時,$\frac{n}{x}=k$;$x=l$時,$\frac{n}{x}=k+\varepsilon_1$。於是取個整,$x\in[l,r]$的範圍,$\lfloor\frac{n}{x}\rfloor=k$

下面的都可以這樣理解。

給定$n$,求$\lfloor\frac{n}{ai+b}\rfloor$的整除分塊

先令$i'=ai+b$,那麼$l'=al+b$,$r'=ar+b$。 令$k=\lfloor \frac{n}{l'} \rfloor$,$r'$是最大的$i'$使得$ki'\le n$,那麼$r'=\lfloor \frac{n}{k} \rfloor$

帶入$l'=al+b$,得到$k=\lfloor \frac{n}{al+b} \rfloor$ ,$r'=\left\lfloor \frac{n}{\lfloor \frac{n}{al+b} \rfloor} \right\rfloor$

$r'=ar+b=\left\lfloor \frac{n}{\lfloor \frac{n}{al+b} \rfloor} \right\rfloor$ ,那麼$r=\left\lfloor\frac{\left\lfloor \frac{n}{\lfloor \frac{n}{al+b} \rfloor} \right\rfloor-b}{a}\right\rfloor$

給定$n$,求$\lfloor\frac{n}{i^2}\rfloor$的整除分塊

令$i'=i^2$,那麼$l'=l^2$,$r'=r^2$。令$k=\lfloor\frac{n}{l'}\rfloor$,$r'$是最大的$i'$使得$ki'\le n$,那麼$r'=\lfloor\frac{n}{k}\rfloor=\left\lfloor\frac{n}{\lfloor\frac{n}{l^2}\rfloor}\right\rfloor$

$r'=r^2=\left\lfloor\frac{n}{\lfloor\frac{n}{l^2}\rfloor}\right\rfloor$ ,得到$r=\left\lfloor\sqrt{\left\lfloor\frac{n}{\lfloor\frac{n}{l^2}\rfloor}\right\rfloor}\right\rfloor$

給定$n$,求$\lceil\frac{n}{i}\rceil$的整除分塊

由於$\lceil\frac{n}{i}\rceil=\lfloor\frac{n+i-1}{i}\rfloor$ ,令$k=\lfloor\frac{n+l-1}{l}\rfloor$,$r$是最大的$i$使得$ik\le n+i-1$,那麼$r=\lfloor\frac{n-1}{k-1} \rfloor=\left\lfloor\frac{n-1}{\lfloor\frac{n+l-1}{l}\rfloor-1}\right \rfloor$

$log$分塊

$\sum\limits_{i=1}^{n}\lfloor log_ai \rfloor$

檢視程式碼
for (int l = 1, r, k = (int)(log(l) / log(a));l <= n;l = r + 1, k++) {
    r = min(l * a - 1, n);
    res += k * (r - l + 1);
}

積性函式大賞

積性函式:一個定義域為正整數$n$的算術函式$f(n)$,有如下性質:$f(1)=1$,且當$a,b$互質時,$f(ab)=f(a)f(b)$。如果一個數字$n=p_1^{c_1} p_2^{c_2} \cdots p_k^{c_k}$,那麼$f(n)=f(p_1^{c_1})f(p_2^{c_2}) \cdots f(p_k^{c_k})$,那麼研究一個積性函式$f$可以轉化為研究$f(p^c)$

完全積性函式:$f(1)=1$,且對任意兩個正整數$a,b$都有$f(ab)=f(a)f(b)$

性質1:兩個積性函式的狄利克雷卷積必定是積性函式。

性質2:狄利克雷卷積滿足交換律,結合律,分配律。交換律即$f*g=g*f$,也就是$\sum\limits_{d|n}f(d)g(\frac{n}{d})=\sum\limits_{d|n}f(\frac{n}{d})g(d)$。分配律即$(f+g)*h=f*h+g*h$。結合律即$(f*g)*h=f*(g*h)$

1.

單位函式:$\varepsilon(n)=[n=1]$ (完全積性)

2.

冪函式:$id_k(n)=n^k$ (完全積性)

3.

常數函式:冪函式中,$k=0$時, $id_0(n)=1(n)=1$ ,或作$I(n)=1$ (完全積性)

4.

恆等函式:冪函式中,$k=1$時, $id_1(n)=id(n)=n$ (完全積性)

5.

除數函式:$\sigma_k(n)$,$n$所有正因數的$k$次冪之和。 這裡$k$可以是任何複數和實數。

求解:

先來看$k=1$的情況,也就是約數和。

$(1+p_1+...+p_1^{c_1})...(1+p_n+...+p_n^{c_n})=\prod\limits_{i=1}^{n}(\sum\limits_{j=0}^{c_j}p_i^j)$

每個括號裡面就是每一個質數的任一個次冪,就是每個括號裡面選一個數字乘起來,再把所有選擇加起來。顯然這就是約束之和。

那麼$k$次冪,顯然就是$\prod\limits_{i=1}^{n}(\sum\limits_{j=0}^{c_j}(p_i^j)^k)=\prod\limits_{i=1}^{n}(\sum\limits_{j=0}^{c_j}(p_i^k)^j)$,顯然可以用等比數列求和。

例題

用PR分解longlong範圍質因子,然後用上式計算。

檢視程式碼
 void Solve(int TIME) {

    int n, k;cin >> n >> k;
    Prime_factor(n);
    map<int, int> mp;
    for (auto i : res) {
        while (n % i == 0) mp[i]++, n /= i;
    }
    int res = 1;
    for (auto [i, j] : mp) {
        int p = i;p = qp(p, k);
        res = res * (qp(p, j + 1) - 1) % MOD * inv(p - 1) % MOD;
    }
    cout << res << endl;

}

6.

因數個數函式:除數函式中,$k=0$時,$\sigma_o(n)=d(n)=\prod\limits_{i=1}^{k}(c_i+1)$ ,$n$的正因數個數,$n=p_1^{c_1}p_2^{c_2}\cdots p_k^{c_k}$

7.

因數和函式:除數函式中,$k=1$時,$\sigma_1(n)=\sigma(n)=\prod\limits_{i=1}^{k}\sum\limits_{j=0}^{c_i}p_i^j$ ,$n$的所有正因數之和,$n=p_1^{c_1}p_2^{c_2}\cdots p_k^{c_k}$

8.

莫比烏斯函式:$\mu(n)=\begin {cases} 1,n=1\\0,n含有平方因子\\(-1)^k ,n=\prod\limits_{i=1}^{k}p_i,p_i為質數 \end{cases} $

性質:$\sum\limits_{d|n}\mu(d)=\varepsilon(n)$ 即$\mu*1=\varepsilon$ 。這意味著在狄利克雷卷積中,$\mu$是常數函式$1$的逆元。

證明:設$n=\prod\limits_{i=1}^{k}p_i^{c_i}$,$n'=\prod\limits_{i=1}^{k}p_i$ ,那麼$\sum\limits_{d|n}\mu(d)=\sum\limits_{d|n'}\mu(d)=\sum\limits_{i=0}^{k}C_{k}^{i}(-1)^i=(1+(-1))^k=[k=0]=[n=1]$

擴充套件:$[gcd(i,j)=1]=\sum\limits_{d|gcd(i,j)}\mu(d)$ ,將$n$直接用$gcd(i,j)$替換即可

9.

尤拉函式:$\varphi(n)=\sum\limits_{i=1}^{n}[gcd(i,n)=1]$ ,表示$[1,n]$中與$n$互質的數的個數

計算公式: $n=\prod\limits_{i=1}^{k}p_i^{c_i}$,$p_i$為質數 。 $\varphi(n)=n(1-\frac1{p_1})(1-\frac1{p_2})\cdots(1-\frac1{p_k})$

感性的證明:以$12$舉例,$12$有兩個質因子$2,3$。在$[1,12]$中,有$\frac1{2}$的數是$2$的倍數,所以有$1-\frac1{2}$是數不是$2$的倍數,在這不是$2$的倍數的數中,有$\frac{1}{3}$的數是3的倍數,所以既不是$2$的倍數也不是$3$的倍數的數字有$12(1-\frac1{2})(1-\frac{1}{3})$ 。

性質1:若$p$是質數,$\varphi(p)=p-1$ 。

性質2:若$p$是質數,$n=p^k$,$\varphi(n)=p^k-p^{k-1}$ 。證明:$\varphi(n)=n(1-\frac{1}{p})=p^k(1-\frac1{p})=p^k-p^{k-1}$

性質3:$\sum\limits_{d|n} \varphi(d)=n $即 $\varphi*1=id$ 。

證明:$n=\prod\limits_{i=1}^{k}p_i^{c_i}$,由於$\varphi$為積性函式,故只需要證明當$n'=p^c$時,$\varphi*1=\sum\limits_{d|n'}\varphi(d)1(\frac{n'}{d}) = id$

顯然$d=p^0,p^1,p^2,...,p^c$ ,那麼$\sum\limits_{i=0}^{c}\varphi(p^i)=\sum\limits_{i=0}^{c}p^i(1-\frac{1}{p})=1+(1-\frac{1}{p})\sum\limits_{i=1}^cp^i=1+(1-\frac{1}{p})\frac{p(1-p^c)}{1-p}=p^c=id(n')$

狄利克雷函式與尤拉函式的聯絡:對$\varphi*1=id$兩邊同時捲上$\mu$,得到$\varphi*1*\mu=id*\mu$

由於狄利克雷卷積滿足結合律,$\varphi*\varepsilon=id*\mu$即$\varphi=\mu*id$

比較有用的式子

根據定義 $\varphi(n)=\sum\limits_{i=1}^{n}[gcd(i,n)=1]=\sum\limits_{i=1}^{n-1}[gcd(i,n)=1]+\varepsilon(n)$

$\sum\limits_{i=1}^{n}\varphi(i)=\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{i-1}[gcd(i,j)=1]+1$,即有序對的貢獻再加上$1$ 。

那麼$\sum\limits_{i=1}^{n}\varphi(i)=\frac12\left(\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}[gcd(i,j)=1]-\sum\limits_{i=1}^{n}[gcd(i,i)=1]\right)+1=\frac12\left(\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}[gcd(i,j)=1]+1\right)$

即$\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}[gcd(i,j)=1]=2\sum\limits_{i=1}^{n}\varphi(i)-1$

10.

$\omega(n)$表示$n$的質因子數目(不可重複) ,例如$n=p_1^{c_1} p_2^{c_2} \cdots p_k^{c_k}$ ,$\omega(n)=k$

11.

$\Omega(n)$表示$n$的質因子數目(可重複) ,例如$n=p_1^{c_1} p_2^{c_2} \cdots p_k^{c_k}$ ,$\Omega(n)=\sum\limits_{i=1}^{k}c_i$

12.

劉維爾函式:$\lambda(n)=(-1)^{\Omega(n)}$

性質:$\sum\limits_{d|n}\lambda(d)=\begin{cases} 1,若n是平方數\\0,若n不是平方數 \end{cases}$ ,$\lambda(n)=\sum\limits_{d^2|n}\mu(\frac{n}{d^2})$

13.

$\gamma(n)=(-1)^{\omega(n)}$

14.

最大公約數:$gcd(n,k)$當$k$固定時,是積性函式

莫比烏斯反演

因數形式

$f(n)=\sum\limits_{d|n}g(d) ⟺ g(n)=\sum\limits_{d|n}\mu(d)f(\frac{n}{d})$

即$f=g*1 ⟺ g=\mu*f$

證明:

兩邊同時乘以$\mu$得到$f*\mu=g*1*\mu=g*\varepsilon=g$

莫比烏斯容斥

跟莫比烏斯反演關係不大,但是因為很有用並且用到了莫比烏斯函式,所以也放在這裡

從埃篩到莫比烏斯容斥

用$cnt_i$表示$1\sim n$中,$i$出現的次數,顯然$cnt_i=1$

用$k_i$表示$1\sim n$中,$i$的倍數出現的次數。

如何用$k$來表示出$cnt$呢?

這裡不妨設$n=10$。

1 2 3 4 5 6 7 8 9 10
1 1 1 1 1 1 1 1 1 1
 -1  -1  -1  -1  -1
   -1    -1    -1
       -1        -1
          1
           -1
                  1

$cnt_1=k_1-k_2-k_3-k_5+k_6-k_7+k_{10}$,首先篩$1$的倍數,給每個$1$的倍數都加$1$,然後篩$2$的倍數,給每個$2$的倍數減去$1$,然後篩$3$的倍數,也給每個$3$的倍數都減去$1$。然後發現$4$已經被篩過了,跳過。然後是$5$的倍數,也都減去$1$。接下來是$6$的倍數,發現它被重複減了一次,於是列舉$6$的倍數,加回來一次。接下來篩$7$的倍數,給每個都減去$1$。接下來發現$8$的倍數已經篩過了,於是跳過。然後$9$也篩過了,跳過。最後$10$的倍數,發現多減了一次,加回來。

找一下有什麼規律?$2$,$3$,$5$,$7$都包含了奇數個質因子所以是減去,$6$和$10$則包含了偶數個質因子所以是加上,而$4$,$8$,$9$包含了相同的質因子多次,也就是有平方數因數。接下來特殊定義$1$的時候是加上。

這個時候發現它恰好滿足莫比烏斯函式的定義。

多推點式子看看:$cnt_2=k_2-k_4-k_6-k_{10}=k_{2\cdot 1}-k_{2\cdot 2}-k_{2\cdot 3}-k_{2\cdot 5}$

於是得到一個通式:$cnt_x=\sum\limits_{i=1}^{\lfloor\frac{n}{x}\rfloor}k_{x\cdot i}\mu_i$

做些推廣就得到了莫比烏斯容斥:

設$f(x)$表示$1\sim n$中$x$出現的次數($x$的貢獻)

$g(x)$表示$1\sim n$中有多少個是$x$的倍數($x$的倍數的貢獻)

則有$f(x)=\sum\limits_{i=1}^{\lfloor \frac{n}{x} \rfloor}g(i\cdot x)\mu(i)$

例如:

對於$g(x)=\sum\limits_{i=1}^n\sum\limits_{j=1}^{n} [gcd(a_i,a_j)=x]$的形式,可以令$f(d)=\sum\limits_{x=d,2d,...}g(x)=\sum\limits_{i=1}^n\sum\limits_{j=1}^{n}][d|gcd(a_i,a_j)]=\sum\limits_{i=1}^n\sum\limits_{j=1}^{n}[d|a_i][d|a_j]=(\sum\limits_{i=1}^{n}[d|a_i])^2$

$f(x)$可以$O(nlogn)$時間複雜度求出。

於是就有$g(d)=\sum\limits_{x=d,2d,...} f(x)\mu(\frac{x}{d})$

即$g(x)=\sum\limits_{n=x,2x,...} f(n)\mu(\frac{n}{x})=\sum\limits_{n=x,2x,...}(\sum\limits_{i=1}^{n}[n|a_i])^2\mu(\frac{n}{x})$

也可以$O(nlogn)$複雜度求出。

尤拉反演

利用$id=\varphi*1$,即$F(x)=\sum\limits_{d|F(x)}\varphi(d)$

例如:

對於$f(x)=\sum\limits_{i=1}^n\sum\limits_{j=1}^{n}gcd(a_i,a_j)$的形式,可以得到$f(x)=\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}\sum\limits_{d|gcd(a_i,a_j)} \varphi(d)=\sum\limits_{i=1}^{n}[d|a_i]\sum\limits_{j=1}^{n}[d|a_j]\sum\limits_{d=1}^{n} \varphi(d)$

矩陣樹定理

$Laplacian$矩陣

$g(i,i)$表示$i$的度數,入度+出度。

如果$i$和$j$ $(i\neq j)$之間有邊的話,就給對應的位置$g(i,j)$填上$-1$,否則為$0$。如果有重邊,那麼就改成對應的負幾即可。

無向圖生成樹個數

$Laplacian$矩陣任選一個$i$後,去掉$i$行$i$列的矩陣的行列式。

生成樹權值

邊權的乘積。那麼可以用有重邊的生成樹來表示所有生成樹的權值之和。這個很容易理解,對於重邊的圖,生成樹個數顯然是每個邊數乘起來後再乘上去重後的生成樹個數。

有向圖的生成樹

以$r$為根的生成樹,並且$r$能到達所有點。

有向圖的生成樹個數

以$r$為根的生成樹的個數是,在$Laplacian$矩陣去掉$r$行$r$列的矩陣的行列式。

會用到的板子

輾轉相除求行列式
 int g[N][N];

int det(int n) {
    int ans = 1;
    for (int i = 1;i <= n;i++) {
        for (int j = i + 1;j <= n;j++) {
            //消掉g[j][i]
            int x = i, y = j;
            while (g[x][i]) {
                int t = g[y][i] / g[x][i];
                for (int k = i;k <= n;k++) {
                    g[y][k] = (g[y][k] - t * g[x][k]) % MOD;
                }
                swap(x, y);
            }
            //g[x][i]=0
            if (x == i) {
                for (int k = i;k <= n;k++) {
                    swap(g[i][k], g[j][k]);
                }
                ans = -ans;
            }
        }
        if (g[i][i] == 0){
            return 0;
        }
        ans = ans * g[i][i] % MOD;
    }
    if (ans < 0) ans += MOD;
    return ans;
}

void Solve(int TIME) {

    int n, m;cin >> n >> m;
    for (int i = 1;i <= m;i++) {
        int u, v;cin >> u >> v;
        g[u][v]--, g[v][u]--;
        g[u][u]++, g[v][v]++;
    }

}

超幾何分佈

$n$個物品,其中有$m$個次品。不放回的取$k$個物品,有$x$個次品的機率為$\frac{C_{m}^{x}C_{n-m}^{k-x}}{C_{n}^{k}}$。 從組合的角度很容易理解

01分數規劃

給定兩個陣列,$a_i$表示選取$i$的收益,$b_i$表示選取$i$的代價,定義$x[i]=1$或$0$,表示選或不選,每種物品只有選或不選兩種方案,求出$\frac{\sum\limits_{i=1}^{n}a_i x_i}{\sum\limits_{i=1}^{n}b_i x_i }$的最大值/最小值

二分

假設我們求最大值,二分一個答案$mid$,當存在一組$x_i$,滿足$\frac{\sum\limits_{i=1}^{n}a_i x_i}{\sum\limits_{i=1}^{n}b_i x_i }>mid$,則說明當前的$mid$可以。

$\frac{\sum\limits_{i=1}^{n}a_i x_i}{\sum\limits_{i=1}^{n}b_i x_i }>mid \;⇒\; \sum\limits_{i=1}^{n}a_ix_i-mid\sum\limits_{i=1}^{n}b_ix_i>0 \;⇒\;\sum\limits_{i=1}^{n}x_i(a_i-mid \cdot b_i)>0$

只要求出上述式子的最大值就可以了,如果最大值比$0$大,說明$mid$是可行的,否則不可行。

如果要求最小值,只要比$0$小即可。

二分求最小值
 const ld eps = 1e-8;
ld l = eps, r = 1e9;
while (r - l > eps) {
    ld mid = (l + r) / 2;
    vc<ld> g;
    for (auto [x, y, a, b] : edge) {
        g.push_back(a - b * mid);
    }
    sort(g.begin(), g.end());
    ld res = 0;
    for (int i = 0;i < k;i++) {//選k個物品
        res += g[i];
    }
    if (res < eps) r = mid;//ans
    else l = mid;
}
二分求最大值
 const ld eps = 1e-8;
ld l = eps, r = 1e9;
while (r - l > eps) {
    ld mid = (l + r) / 2;
    vc<ld> g;
    for (auto [x, y, a, b] : edge) {
        g.push_back(-(a - b * mid));
    }
    sort(g.begin(), g.end());
    ld res = 0;
    for (int i = 0;i < k;i++) {//取k個物品
        res += g[i];
    }
    if (res > eps) l = mid;//ans
    else r = mid;
}

$Dinkelbach$演算法

思想是每次用上一輪的答案當做新的$mid$來輸入,不斷地迭代,直至答案滿足精度。比二分更快。

Dinkelbach求最小值
 const ld eps = 1e-8;
ld ans = 1e9;
while (1) {
    DSU d(n + 1);
    vc<pair<ld, ai2>> g;
    for (auto [x, y, a, b] : edge) {
        g.push_back({ a - b * ans,{a,b} });
    }
    sort(g.begin(), g.end());
    for (int i = 0;i < k;i++)//取k個物品
        A += g[i].second.at(0), B += g[i].second.at[1];
    ld t = 1.0 * A / B;
    if (abs(t - ans) < eps) break;
    ans = t;
}
Dinkelbach求最大值
 const ld eps = 1e-8;
ld ans = eps;
while (1) {
    DSU d(n + 1);
    vc<pair<ld, ai2>> g;
    for (auto [x, y, a, b] : edge) {
        g.push_back({ -(a - b * ans),{a,b} });
    }
    sort(g.begin(), g.end());
    for (int i = 0;i < k;i++)//取k個物品
        A += g[i].second.at(0), B += g[i].second.at[1];
    ld t = 1.0 * A / B;
    if (abs(t - ans) < eps) break;
    ans = t;
}


求二元一次不定方程

給定$a,b,c$ ,求$ax+by=c$中$x$和$y$的整數解。

首先用exgcd求出一組特解$x_0,y_0$,然後有$ax_0+by_0=gcd(a,b)=d$,如果$c \;mod\;d \neq 0$則無解。

令$base=\frac{c}{d}$,則有$a(x_0\cdot base+\frac{b}{gcd(a,b)})+b(y_0\cdot base-\frac{a}{gcd(a,b)})=c$ 。則有通解為$\begin{cases} x=x_0 \cdot \frac{c}{d} +k\cdot\frac{b}{gcd(a,b)}\\y=y_0\cdot \frac{c}{d}-k\cdot\frac{a}{gcd(a,b)} \end{cases}$ 。則很顯然只要對$x_0\cdot \frac{c}{d}$關於$\frac{b}{gcd(a,b)}$取模就能得到$x$的最小非負整數解,對$y$同理,只要對$y_0\cdot \frac{c}{d}$關於$\frac{a}{gcd(a,b)}$取模就能得到$y$的最小非負整數解

模板題:P5656 二元一次不定方程

如果是非負整數,則是完全揹包和同餘最短路相關。具體見圖論。

$Lucas$定理

求$C_n^m \;mod\; P$,$P$為質數

將$n$寫成$P$進位制數,獲得$n_0P^0+n_1P^1+n_2P^2+...$

將$m$寫成$P$進位制數,獲得$m_0P^0+m_1P^1+m_2P^2+...$

則$C_n^m \;mod\; P=C_{n_0}^{m_0}\cdot C_{n_1}^{m_1}\cdot C_{n_2}^{m_2}... \;mod\;P$

階,原根,指數方程

:滿足$a^x \equiv 1(\mod m)$ 最小的正整數$x$,讀作$a$模$m$的階。當前僅當$gcd(a,m)=1$這個方程會有解。

由尤拉公式我們知道,當$\gcd(a,m)=1$,一定有$a^{\varphi(m)}=1 (\mod m)$

階可以理解為,從$1$開始不停的乘$a(\mod m)$,一個迴圈的長度。

那麼如果$a^x\equiv 1(\mod m)$,那麼一定有$階|x$ 。

因此我們知道,一定有$階|\varphi(m)$,也就是說如果要回到迴圈原點,步數一定是環長的倍數。

於是過程是這樣的,將$\varphi(m)$用唯一分解定理表示成$\varphi(m)=p_1^{e_1}p_2^{e_2}...p_k^{e_k}$,對於每個質因子,如果去掉一個之後還是階的倍數,即$a^{\frac{x}{p_i}}=1(\mod m)$,那麼就去掉。

原根:$g\mod m$的階為$\varphi(m)$,那麼$g$為$m$的原根。

結論:只有$1$,$2$,$4$,$p^a$,$2p^a$存在原根,這裡的$p$為奇素數。

原根不是唯一的。最小的原根不會很大。原根的個數為$\varphi(p-1)$個。

從小到大列舉或隨機$g$,然後判斷是否是原根。跟上面差不多,分解為若干質數$p_i$後,判斷每個$p_i$是否都有$g^{\frac{\varphi(m)}{p_i}} \neq 1(\mod a)$即可。

需要注意的是,$p$的原根不一定是$p^2$的原根

當$g$為原根時,由於$1$到$m$之間與$m$互質的數有$\varphi(m)$個, 又因為原根的性質得到迴圈節長度是$\varphi(m)$,又知道迴圈節裡面每個數之間都是互不相同的。所以這裡面的任意一個數都可以用$g^i$表示。

指數方程:求$a^x \equiv b(\mod m)$,這裡$\gcd(a,m)=1$

由尤拉定理,我們知道這個方程如果有解,答案一定是在$0$到$\varphi(m)-1$之間,因此最暴力的做法就是for迴圈累乘,但是這樣複雜度就是比較高的。

這裡運用meet in the middle的思想,令$T=\lceil \sqrt{m} \rceil$,那麼$0$到$m-1$的數字一定可以表示成$qT+r$的形式,其中$q\in[0,T-1]$,$r\in [0,T-1]$。那麼這個方程可以表示成$a^{qT+r}\equiv b(\mod m)$。

移項可得$(a^T)^q \equiv b\cdot a^{-r} (\mod m)$。就相當於在兩個陣列$a^{T\cdot 0},a^{T\cdot 1},...,a^{T\cdot (T-1)}$和$b\cdot a^{-0},b\cdot a^{-1},...,b\cdot a^{-(T-1)}$裡面找是否存在兩個相同的數字。

程式碼中,為了方便起見,用$x=qT-r$表示,其中$q\in[1,T]$,$r\in [1,T]$

那麼就會變成$a^{qT} \equiv b \cdot a^r $,這樣這個$r$就不需要求逆元,方便一點。

檢視程式碼
 int bsgs(int a, int b, int mod) {
    a %= mod, b %= mod;
    int T = sqrt(mod) + 2;
    int v = qp(a, T, mod), d = 1;
    unordered_map<int, int> hs;
    for (int q = 1;q <= T;q++) {
        d = d * v % mod;
        if (!hs.count(d)) hs[d] = q * T;
    }

    int ans = mod + 1;
    d = b;
    for (int r = 1;r <= T;r++) {
        d = d * a % mod;
        if (hs.count(d)) ans = min(ans, hs[d] - r);
    }
    if (ans >= mod) return -1;
    else return ans;

}

容斥原理

容斥原理的基本形式

$|S-\bigcup\limits_{i=1}^{n}A_i|=\sum\limits_{i=0}^{n}(-1)^{i}\sum\limits_{1\le j_1 \le j_2 \le...\le j_i \le n} |\bigcap\limits_{k=1}^{i}A_{j_k}|$

容斥原理的符號形式

設$S$是一個有限集,$a_1,...,a_n$是$n$種性質。

記$N(a_i)$為$S$中有$S$中有$a_i$性質的元素的數量。特殊的,記$N(1)=|S|$。

記$N(1-a_i)$為$S$中沒有$a_i$性質的元素的數量。

$N(a_{i_1}a_{i_2}...a_{i_k})$為$S$中同時有$a_{i1},a_{i2},...,a_{i_k}$性質的元素數量。

記$N(a\pm b)=N(a) \pm N(b)$

那麼容斥原理可以寫成

$N((1-a_1)(1-a_2)...(1-a_n))=\sum\limits_{i=0}^{n}(-1)^i \sum\limits_{1\le j_1 <j_2<...<j_i \le n}N(a_{j_1}a_{j_2}...a_{j_i})$

$N(a_1...a_x(1-a_{x+1})...(1-a_{x+n}))=\sum\limits_{i=0}^{n}(-1)^i\sum\limits_{x \le j_1 <j_2<...<j_i \le x+n} N(a_1...a_x a_{j_1}...a_{j_i})$

而交集可以表示為$N(a_1a_2...a_n)$,並集表示為$N(1-(1-a_1)(1-a_2)...(1-a_n))$表示至少有一個滿足條件的補集。

表達成了代數形式,可以簡化一些問題的推導過程。

例題1:求正整數$1$到$n$中,既不是2的倍數而不是3的倍數,但是是5的倍數的數的數量。

設$a_1:是2的倍數 ,a_2:是3的倍數,a_3:是5的倍數$

$N((1-a_1)(1-a_2)a_3)=N(a_3-a_1a_3-a_2a_3+a_1a_2a_3)$

$=N(a_3)-N(a_1a_3)-N(a_2a_3)+N(a_1a_2a_3)=\lfloor\frac{n}{5}\rfloor-\lfloor\frac{n}{10}\rfloor-\lfloor\frac{n}{15}\rfloor+\lfloor\frac{n}{30}\rfloor$

例題2:剛好存在$k$個$i$滿足$p_i=i$的排列個數。

設$a_i: \; p_i=i$

$N(a_{1}...a_{k}(1-a_{k+1})...(1-a_n))=\sum\limits_{i=0}^{n-k}(-1)^iC_{n-k}^{i}N(a_1...a_ka_{j_i}...a_{j_i})$

$=\sum\limits_{i=0}^{n-k}(-1)^iC_{n-k}^{i}(n-(k+i))!$

又因為前面這$k$個是任取的,所以要總體再乘上$C_{n}^{k}$ 。答案就是$C_n^k\sum\limits_{i=0}^{n-k}(-1)^iC_{n-k}^{i}(n-(k+i))!$

這個形式可以窺見一些二項式反演的跡象,有時候使用比二項式反演還要方便,但有時候還是二項式反演方便。建議把兩個方法都熟練掌握。

位運算常見結論

$a|b + a\&b =a+b$

$a|b-a\&b=a\text{^} b$

$a-b=2a-(a+b)=2a-a|b-a\&b$

可以從集合的角度畫個圖理解:兩個集合的總和是兩個集合的交集加上並集,兩個集合的異或是兩個集合的並集減去兩個集合的交集。

裴蜀定理

$a,b$是不全為$0$的整數,對任意整數$x,y$,滿足$gcd(a,b) \;|\; ax+by$,且存在整數$x,y$,使得$ax+by=gcd(a,b)$。

推論:在長為$n$的迴圈陣列(即陣列中的最後一個元素的下一個元素是陣列中的第一個元素,陣列中第一個元素的前一個元素是陣列中的最後一個元素)中,如果有周期$n$和$k$($n$是迴圈陣列自帶的性質導致的),有$a_i=a_{i+nx+ky}=a_{i+gcd(n,k)}$ 。

勒讓德定理

$n!$做質因子分解後,質因子$p$的次數為$\sum\limits_{k=1}\lfloor\frac{n}{p^k} \rfloor$。

證明:這$n$個數的連乘積中,只有$p、2p、...、\lfloor \frac{n}{p} \rfloor \cdot p$能被$p$整除。從而有了$\lfloor \frac{n}{p} \rfloor$個質因子$p$ 。只有$p^2、2p^2、...、\lfloor \frac{n}{p^2} \rfloor\cdot p^2$,考慮到每個能被$p^2$整除的數都被$p$整除,所以只需要加上$\lfloor\frac{n}{p^2}\rfloor$個即可,以此類推即是結果。

$[n,2n]$之間必然存在質數

證明很複雜,但是可以用哥德巴赫猜想做偽證明。顯然$2n$是個偶數,那麼右哥猜得到存在兩個質數$p+q=2n$,如果$[n,2n]$不存在質數,那麼$p<n,q<n$,不成立。

加強:伯特蘭-切比雪夫定理,若整數$n>3$,則至少存在一個質數$p$,符合$p\in(n,2n-2)$。

線性規劃

最大化目標函式:

需要最大化$\boldsymbol {C^{T}X} $,即$c_1x_1+...+c_mx_m$

其中$\boldsymbol X\ge 0$ $\boldsymbol {AX}\le \boldsymbol B$

其中$\boldsymbol A$是一個$n\times m$的矩陣,$\boldsymbol B$是一個$n$維向量,$\boldsymbol X$是一個$m$維向量。

$n$和$m$表示約束的個數和變數的個數。引數$\boldsymbol a$和$\boldsymbol b$表示線性規劃問題的係數矩陣和約束向量。

線性代數

向量空間

向量空間是由向量組成的線性空間,定義$(F,V,+,\cdot)$​為向量空間,其中$F$​為域,$V$​為集合,$V$​中元素稱為向量,$+$​為向量加法,$\cdot $​為標量乘法。

線性相關

對於向量空間中$V$上$n$個元素的向量組$(\boldsymbol{v}_{1},...,\boldsymbol{v}_{n})$,若存在不全為$0$的數$a_i\in F$,滿足$a_1\boldsymbol v_1+a_2\boldsymbol v_2+...+a_n\boldsymbol v_n=0$,則稱這$n$個向量線性相關,否則稱為線性無關。

線性組合

對於向量空間中$V$上$n$個元素的向量組$(\boldsymbol{v}_{1},...,\boldsymbol{v}_{n})$,其線性組合是如下形式的向量,$a_1\boldsymbol v_1+a_2\boldsymbol v_2+...+a_n\boldsymbol v_n$,其中$a_1,...,a_n\in F$。一組向量線性無關$\Longleftrightarrow$沒有向量可用有限個其他向量線性組合所表示

張成

對於向量空間中$V$上$n$個元素的向量組$(\boldsymbol{v}_{1},...,\boldsymbol{v}_{n})$,其所有線性組合所構成的集合稱為$(\boldsymbol{v}_1,...,\boldsymbol{v}_{n})$的張成,記為$span(\boldsymbol{v}_{1},...,\boldsymbol{v}_{n})$。

若向量空間$V$中向量組$\mathcal B$既是線性無關的又可以張成$V$,則稱其為$V$的基。

$\mathcal B$​中的元素稱為基向量,如果基中的元素個數有限,就稱向量空間為有限維向量空間,將元素的個數稱作向量空間的維數。

性質:設$\mathcal B$是向量空間$V$的基,則$\mathcal B$具有以下性質:

1.$V$是$\mathcal B$的最小生成集,就是說只有$\mathcal B$能張成$V$,而它的任何真子集都不張成全部的向量空間。

2.$\mathcal B$是$V$中線性無關向量的極大集合,就是說$\mathcal B$在$V$中是線性無關集合,而且$V$中沒有其他線性無關集合包含它作為真子集。

3.$V$中所有向量都可以按唯一的方式被表達為$\mathcal B$中向量的線性組合。

線性相關性引理

如果$(\boldsymbol v_1,...,\boldsymbol v_n)$在$V$中是線性相關的,並且$\boldsymbol v_1\ne 0$,則有至少一個$j\in \{2,...,n\}$使得下列成立:

1. $\boldsymbol v_j\in span(\boldsymbol v_1,...,\boldsymbol v_{j-1})$
2. 如果從$(\boldsymbol v_1,...,\boldsymbol v_n)$去掉第$j$項,則剩餘向量組的張成仍然等於$span(\boldsymbol v_1,...,\boldsymbol v_n)$

下面介紹演算法競賽中的線性基

異或線性基

對於數$a_0,a_1,...,a_n$將$a_i$的二進位制表示$(b_m...b_0)_2$看作一個向量$\boldsymbol a_i=(b_m,...,b_0)$。

下文稱向量$\boldsymbol a_i$的第$j$位為$\boldsymbol a_{i,j}$。

向量組$\boldsymbol a_1,...,\boldsymbol a_n$可以張成一個向量集合$span(\boldsymbol a_1,...,\boldsymbol a_n)$,加上異或運算和乘法運算,即可形成一個向量空間$V=(\{0,1\},span(\boldsymbol a_1,...,\boldsymbol a_n),\text{^} ,\cdot )$。

性質:對於任意存在於線性基的二進位制位$i$,至多隻有一個$\boldsymbol a$滿足第$i$位為$1$。

模擬這個過程,$n=5$,$a={7,1,4,3,5}$。

一開始矩陣是$\left[\begin{array}{l} 0&0&0 \\0&0&0\\0&0&0 \end{array}\right]$,

加入$7=(111)_2$,矩陣變成:$\left[\begin{array}{l} 1&1&1 \\0&0&0\\0&0&0 \end{array}\right]$

加入$1=(001)_2$,新增到最後一行,同時消去第一行的最低位,矩陣變成:$\left[\begin{array}{l} 1&1&0 \\0&0&0\\0&0&1 \end{array}\right]$,

加入$4=(100)_2$,由於第一行已經有數字,它被異或為$(010)_2$,加入第二行,同時消去第一行的第二位,矩陣變成:$\left[\begin{array}{l} 1&0&0 \\0&1&0\\0&0&1 \end{array}\right]$.

剩下的數字都加不上了。

威爾遜定理

$(p-1)! \equiv-1(mod \;p)$是$p$為質數的充分必要條件。

驗證充分性:

非質數的情況,帶入$1$發現結果為$0$,帶入$4$結果為$2$。

當$p>4$時,如果$p$是完全平方數,那麼$p=k^2$,由於$p>4$,可得$k>2$,即$2k-p=2k-k^2=-(k-1)^2+1<0$,於是$2k<p$成立。

故$(p-1)!=1\times2\times ...\times k\times...\times 2k\times ...\times (p-1)=2k^2x=2px$,於是$(p-1)! \equiv 0(mod\; p)$。

當$p$不是完全平方數,那麼$p$就必然等於兩個完全不相等的數$a,b$的乘積,不妨設$a<b$,則有$1<a<b<p$,於是$(p-1)!=1\times ...\times a\times...\times b \times...\times (p-1)=abx=px$,於是也有$(p-1)!\equiv 0(mod \;p)$。

驗證必要性:

當$p$為質數,考慮二次剩餘式,$x^2\equiv 1(mod\;p)$ ,移項$x^2-1\equiv 0(mod\;p)$,因式分解$(x+1)(x-1)\equiv 0(mod\;p)$,可以得到$x\equiv p-1(mod\;p)$或者$x\equiv 1(mod\;p)$。拋開這兩個數,對於$a\in [2,p-2]$,必然存在一個和它不相等的逆元$a^{-1}\in [2,p-2]$滿足$aa^{-1}\equiv -1(mod\;p)$。於是必然有$\frac{p-3}{2}$對數相乘的乘積是$1$,即$(p-2)!\equiv 1(mod \;p)$,再乘上$(p-1)$即得到$(p-1)!\equiv p-1(mod\;p)$即$(p-1)!\equiv -1(mod\;p)$。

例題

狄利克雷卷積

1.P2522 - Problem b

知識點

整除分塊,狄利克雷卷積

題意

$\sum\limits_{i=a}^{b}\sum\limits_{j=c}^{d}[gcd(i,j)=k]$

題解

$\sum\limits_{i=a}^{b}\sum\limits_{j=c}^{d}[gcd(i,j)=k]=\sum\limits_{ki=a}^{b}\sum\limits_{kj=c}^{d}[gcd(ki,kj)=k]=\sum\limits_{i=\lceil \frac{a}{k}\rceil}^{\lfloor \frac{b}{k}\rfloor}\sum\limits_{j=\lceil \frac{c}{k}\rceil}^{\lfloor \frac{d}{k}\rfloor}[gcd(i,j)=1]$ (這裡上限是下取整,下限是上取整。這樣不會多列舉。如果反過來的話,就可能多列舉了。)

$=\sum\limits_{i=\lceil \frac{a}{k}\rceil}^{\lfloor \frac{b}{k}\rfloor}\sum\limits_{j=\lceil \frac{c}{k}\rceil}^{\lfloor \frac{d}{k}\rfloor}\sum\limits_{D|gcd(i,j)}\mu(D)=\sum\limits_{D=1}^{min(\lfloor \frac{b}{k}\rfloor,\lfloor \frac{d}{k}\rfloor)}\mu(D)\sum\limits_{i=\lceil \frac{a}{k}\rceil}^{\lfloor \frac{b}{k}\rfloor}\sum\limits_{j=\lceil \frac{c}{k}\rceil}^{\lfloor \frac{d}{k}\rfloor}[D|gcd(i,j)]$

$=\sum\limits_{D=1}^{min(\lfloor \frac{b}{k}\rfloor,\lfloor \frac{d}{k}\rfloor)}\mu(D)\sum\limits_{Di=\lceil \frac{a}{k}\rceil}^{\lfloor \frac{b}{k}\rfloor}\sum\limits_{Dj=\lceil \frac{c}{k}\rceil}^{\lfloor \frac{d}{k}\rfloor}[D|gcd(Di,Dj)]=\sum\limits_{D=1}^{min(\lfloor \frac{b}{k}\rfloor,\lfloor \frac{d}{k}\rfloor)}\mu(D)\sum\limits_{Di=\lceil \frac{a}{k}\rceil}^{\lfloor \frac{b}{k}\rfloor}\sum\limits_{Dj=\lceil \frac{c}{k}\rceil}^{\lfloor \frac{d}{k}\rfloor}1$

$=\sum\limits_{D=1}^{min(\lfloor \frac{b}{k}\rfloor,\lfloor \frac{d}{k}\rfloor)}\mu(D)\sum\limits_{i=\lceil \frac{a}{Dk}\rceil}^{\lfloor \frac{b}{Dk}\rfloor}\sum\limits_{j=\lceil \frac{c}{Dk}\rceil}^{\lfloor \frac{d}{Dk}\rfloor}1=\sum\limits_{D=1}^{min(\lfloor \frac{b}{k}\rfloor,\lfloor \frac{d}{k}\rfloor)}\mu(D)({\lfloor \frac{b}{Dk}\rfloor}-{\lceil \frac{a}{Dk}\rceil}+1)({\lfloor \frac{d}{Dk}\rfloor}-{\lceil \frac{c}{Dk}\rceil}+1)$

整除分塊即可,簡單證明一下兩個整除分塊。

$\lfloor \frac{n}{ik} \rfloor$,令$t=\lfloor \frac{n}{lk} \rfloor$ ,$r$為使得$itk\le n$最大的$i$,那麼$r=\lfloor \frac{n}{tk} \rfloor=\lfloor\frac{n}{k\lfloor \frac{n}{lk} \rfloor} \rfloor$
$\lceil \frac{n}{ik} \rceil=\lfloor\frac{n+ik-1}{ik} \rfloor$,令$t=\lfloor \frac{n+lk-1}{lk} \rfloor$ ,$r$為使得$itk\le n+ik-1$最大的$i$,那麼$r=\lfloor \frac{n-1}{tk-k} \rfloor=\lfloor\frac{n-1}{k\lfloor \frac{n+lk-1}{lk} \rfloor-k} \rfloor$

檢視程式碼
 void Solve(int TIME) {

    int res = 0;
    int t;
    int a, b, c, d, k;cin >> a >> b >> c >> d >> k;
    for (int l = 1, r;l <= min(b / k, d / k);l = r + 1) {
        r = min(b / k, d / k);
        t = (a + l * k - 1) / (l * k); if (k * t - k) r = min(r, (a - 1) / (k * t - k));
        t = b / (l * k);if (k * t) r = min(r, b / (k * t));
        t = (c + l * k - 1) / (l * k); if (k * t - k) r = min(r, (c - 1) / (k * t - k));
        t = d / (l * k);if (k * t) r = min(r, d / (k * t));
        res += (pre_mu[r] - pre_mu[l - 1]) * (1 + b / (l * k) - (a + l * k - 1) / (l * k)) * (1 + d / (l * k) - (c + l * k - 1) / (l * k));
    }
    cout << res << endl;

}

2.P2257 - YY的GCD

知識點

狄利克雷卷積,整除分塊

題意

$\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}[gcd(x,y)\in Prime]$

題解

$\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}[gcd(x,y)\in Prime]=\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}\sum\limits_{d=1}^{min(n,m)}[gcd(i,j)=d][d \in Prime]$

$=\sum\limits_{d=1}^{min(n,m)}\sum\limits_{di=1}^{n}\sum\limits_{dj=1}^{m}[gcd(di,dj)=d][d \in Prime]=\sum\limits_{d=1}^{min(n,m)}\sum\limits_{i=1}^{\lfloor\frac{n}{d} \rfloor}\sum\limits_{j=1}^{\lfloor\frac{m}{d} \rfloor}[gcd(i,j)=1][d \in Prime]$

$=\sum\limits_{d=1}^{min(n,m)}\sum\limits_{i=1}^{\lfloor\frac{n}{d} \rfloor}\sum\limits_{j=1}^{\lfloor\frac{m}{d} \rfloor}\sum\limits_{D|gcd(i,j)}\mu(D)[d \in Prime]$

$=\sum\limits_{d=1}^{min(n,m)}\sum\limits_{i=1}^{\lfloor\frac{n}{d} \rfloor}\sum\limits_{j=1}^{\lfloor\frac{m}{d} \rfloor}\sum\limits_{D=1}^{min(\lfloor\frac{n}{d} \rfloor,\lfloor\frac{m}{d} \rfloor)}\mu(D)[d \in Prime][D|gcd(i,j)]$

$=\sum\limits_{D=1}^{min(\lfloor\frac{n}{d} \rfloor,\lfloor\frac{m}{d} \rfloor)}\mu(D)\sum\limits_{d=1}^{min(n,m)}[d \in Prime]\sum\limits_{Di=1}^{\lfloor\frac{n}{d} \rfloor}\sum\limits_{Dj=1}^{\lfloor\frac{m}{d} \rfloor}[D|gcd(Di,Dj)]$

$=\sum\limits_{d=1}^{min(n,m)}[d \in Prime]\sum\limits_{D=1}^{min(\lfloor\frac{n}{d} \rfloor,\lfloor\frac{m}{d} \rfloor)}\mu(D){\lfloor\frac{n}{dD} \rfloor}{\lfloor\frac{m}{dD} \rfloor}$ (到這一步已經可以整除分塊,但是複雜度不夠)

令$T=dD$,那麼$\sum\limits_{T=1}^{min(n ,m)}{\lfloor\frac{n}{T} \rfloor}{\lfloor\frac{m}{T} \rfloor}\sum\limits_{d|T}[d \in Prime]\mu(\frac{T}{d})$

令$g(d)=[d\in Prime]$,$f(T)=\sum\limits_{d|T}g(d)\mu(\frac{T}{d})$,那麼$\sum\limits_{T=1}^{min(n ,m)}{\lfloor\frac{n}{T} \rfloor}{\lfloor\frac{m}{T} \rfloor}\sum\limits_{d|T}g(d)\mu(\frac{T}{d})=\sum\limits_{T=1}^{min(n ,m)}{\lfloor\frac{n}{T} \rfloor}{\lfloor\frac{m}{T} \rfloor}f(T)$

這個$f(T)$顯然是可以預處理的,然後便做完了。由於只用對質數計算貢獻,複雜度約為$O(NloglogN)$,這是因為質數在自然數的佔比約為$logn$,這樣就對原本的$NlogN$進一步做了最佳化。

總時間複雜度$O(NloglogN+T(\sqrt{n}+\sqrt{m}))$ ,這裡的$log$均以$e$為底

檢視程式碼
 int f[N];
void init(int mod = MOD) {
    np[0] = np[1] = 1;mu[1] = 1;
    for (int i = 2;i < N;i++) {
        if (!np[i]) {
            p.push_back(i);
            mu[i] = -1;
        }
        for (auto j : p) {
            if (i * j < N) {
                np[i * j] = 1;
                if (i % j == 0) {
                    break;
                }
                else {
                    mu[i * j] = -mu[i];
                }
            }
            else {
                break;
            }
        }
    }
    int t = 0;
    for (int i = 1;i < N;i++) {
        if (np[i]) continue;
        for (int j = 1;i * j < N;j++) {
            f[i * j] += mu[j];t++;
        }
    }
    for (int i = 1;i < N;i++) {
        f[i] += f[i - 1];
    }
}

void Solve(int TIME) {

    int n, m;cin >> n >> m;int res = 0;
    int lim = min(n, m);
    for (int l = 1, r;l <= min(n, m);l = r + 1) {
        r = lim;
        if (n / l) r = min(r, n / (n / l));
        if (m / l) r = min(r, m / (m / l));
        res += (n / l) * (m / l) * (f[r] - f[l - 1]);
    }
    cout << res << endl;

}

3.CF1884D - Counting Rhyme

知識點

狄利克雷卷積

題解

注意$a_i\in[1,n]$,這個時候倍數的處理就顯得輕鬆很多。只需要記錄$[1,n]$哪些數字出現,然後在迴圈中列舉這個數字的倍數,打上標記。最壞時間複雜度是$nln(n)$,即$\frac{n}{1}+\frac{n}{2}+...+\frac{n}{n}=n(1+\frac{1}{2}+...+\frac{1}{n})=nln(n)$

設一個集合$S$,裡面包含$[1,n]$中所有不為$a$陣列中任何一個數的倍數的數。

$\sum\limits_{i=1}^{n}\sum\limits_{j=i+1}^{n}\sum\limits_{x\in S}[gcd(a_i,a_j)=x]$

$=\frac{1}{2}\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}\sum\limits_{x\in S}[gcd(a_i,a_j)=x]=\frac{1}{2}\sum\limits_{i=1}^{n}cnt_i\sum\limits_{j=1}^{n}cnt_j\sum\limits_{x\in S}[gcd(i,j)=x]$

$=\frac{1}{2}\sum\limits_{x\in S}[gcd(xi,xj)=x]\sum\limits_{xi=1}^{n}cnt_{xi}\sum\limits_{xj=1}^{n}cnt_{xj}=\frac{1}{2}\sum\limits_{x\in S}\sum\limits_{i=1}^{\lfloor\frac{n}{x}\rfloor}cnt_{xi}\sum\limits_{j=1}^{\lfloor\frac{n}{x}\rfloor}cnt_{xj}[gcd(i,j)=1]$

$=\frac{1}{2}\sum\limits_{x\in S}\sum\limits_{i=1}^{\lfloor\frac{n}{x}\rfloor}cnt_{xi}\sum\limits_{j=1}^{\lfloor\frac{n}{x}\rfloor}cnt_{xj}\sum\limits_{d|gcd(i,j)}\mu(d)=\frac{1}{2}\sum\limits_{x\in S}\sum\limits_{i=1}^{\lfloor\frac{n}{x}\rfloor}cnt_{xi}\sum\limits_{j=1}^{\lfloor\frac{n}{x}\rfloor}cnt_{xj}\sum\limits_{d=1}^{\lfloor\frac{n}{x} \rfloor}\mu(d)[d|gcd(i,j)]$

$=\frac{1}{2}\sum\limits_{x\in S}\sum\limits_{di=1}^{\lfloor\frac{n}{x}\rfloor}cnt_{dxi}\sum\limits_{dj=1}^{\lfloor\frac{n}{x}\rfloor}cnt_{dxj}\sum\limits_{d=1}^{\lfloor\frac{n}{x} \rfloor}\mu(d)[d|gcd(di,dj)]=\frac{1}{2}\sum\limits_{x\in S}\sum\limits_{d=1}^{\lfloor\frac{n}{x} \rfloor}\mu(d)\sum\limits_{i=1}^{\lfloor\frac{n}{dx}\rfloor}cnt_{dxi}\sum\limits_{j=1}^{\lfloor\frac{n}{dx}\rfloor}cnt_{dxj}$

令$T=dx$,那麼$\frac{1}{2}\sum\limits_{x\in S}\sum\limits_{T=1}^{ n}\mu(\frac{T}{x})\sum\limits_{i=1}^{\lfloor\frac{n}{T}\rfloor}cnt_{Ti}\sum\limits_{j=1}^{\lfloor\frac{n}{T}\rfloor}cnt_{Tj}=\frac{1}{2}\sum\limits_{T=1}^{ n}\sum\limits_{ x|T}\mu(\frac{T}{x})[x\in S](\sum\limits_{i=1}^{\lfloor\frac{n}{T}\rfloor}cnt_{Ti})^2$

令$f(T)=\sum\limits_{ x|T}\mu(\frac{T}{x})[x\in S]$,那麼就有$\frac{1}{2}\sum\limits_{T=1}^{n}f(T)(\sum\limits_{i=1}^{\lfloor\frac{n}{T}\rfloor}cnt_{Ti})^2$,$f(T)$可以暴力$nlogn$預處理,而後面的整體暴力求也是$O(nlogn)$複雜度

檢視程式碼
 void Solve(int TIME) {

    int n;cin >> n;
    vi cnt(n + 1);
    vi a(n + 1);for (int i = 1;i <= n;i++) cin >> a[i], cnt[a[i]] += 1;
    vi not_in_s(n + 1);
    for (int i = 1;i <= n;i++) {
        if (cnt[i] == 0) continue;
        for (int j = i;j <= n;j += i) {
            not_in_s[j] = 1;
        }
    }

    vi f(n + 1);

    int res = 0;
    for (int i = 1;i <= n;i++) {
        if (not_in_s[i]) continue;
        for (int j = 1;j * i <= n;j++) {
            f[i * j] += mu[j];
        }
    }

    for (int i = 1;i <= n;i++) {
        int t = 0;
        for (int j = 1;j <= n / i;j++) {
            t += cnt[i * j];
        }
        res += t * t * f[i];
    }

    cout << res / 2 << endl;
}

法2:利用莫比烏斯反演的第二形式

P.S.這種$[gcd(a_i,a_j)=x]$用這個方法會方便很多

$\sum\limits_{i=1}^{n}\sum\limits_{j=i+1}^{n}\sum\limits_{x\in S}[ gcd(a_i,a_j)=x]$

$=\sum\limits_{i=1}^{n}\sum\limits_{j=i+1}^{n}\sum\limits_{x\in S}\sum\limits_{x|D}[D|gcd(a_i,a_j)]\mu(\frac{D}{x})$

$=\sum\limits_{i=1}^{n}[D|a_i]\sum\limits_{j=i+1}^{n}[D|a_j]\sum\limits_{x\in S}\sum\limits_{x|D}\mu(\frac{D}{x})$

$=\sum\limits_{x\in S}\sum\limits_{x|D}\sum\limits_{i=1}^{n}[D|a_i]\sum\limits_{j=i+1}^{n}[D|a_j]\mu(\frac{D}{x})$

$F(D)=\sum\limits_{i=1}^{n}[D|a_i]\sum\limits_{j=i+1}^{n}[D|a_j]=\frac{1}{2}\left((\sum\limits_{i=1}^{n}[D|a_i])^2-\sum\limits_{i=1}^n[D|a_i]\right)$

原式$=\sum\limits_{x\in S}\sum\limits_{x|D}F(D)\mu(\frac{D}{x})$

檢視程式碼
 
    int n;cin >> n;
    vi cnt(n + 1);
    vi a(n + 1);for (int i = 1;i <= n;i++) cin >> a[i], cnt[a[i]] += 1;
    vi not_in_s(n + 1);
    for (int i = 1;i <= n;i++) {
        if (cnt[i] == 0) continue;
        for (int j = i;j <= n;j += i) {
            not_in_s[j] = 1;
        }
    }

    vi f(n + 1);

    for (int i = 1;i <= n;i++) {
        for (int j = 1;j * i <= n;j++) {
            f[i] += cnt[i * j];
        }
    }
    for (int i = 1;i <= n;i++) f[i] = (f[i] * f[i] - f[i]) / 2;
    int res = 0;
    for (int i = 1;i <= n;i++) {
        if (not_in_s[i]) continue;
        for (int j = 1;j * i <= n;j++) {
            res += f[i * j] * mu[j];
        }
    }

    cout << res << endl;

}

4.P3312 - 數表

知識點

狄利克雷卷積,整除分塊

題解

設約數和函式$\sigma$

$\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}\sigma(gcd(i,j))[\sigma(gcd(i,j))\le a]=\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}\sum\limits_{d=1}^{min(n,m)}\sigma(d)[gcd(i,j)=d][\sigma(d)\le a]$

$=\sum\limits_{di=1}^{n}\sum\limits_{dj=1}^{m}\sum\limits_{d=1}^{min(n,m)}\sigma(d)[gcd(di,dj)=d][\sigma(d)\le a]=\sum\limits_{i=1}^{\lfloor\frac{n}{d} \rfloor}\sum\limits_{j=1}^{\lfloor\frac{m}{d} \rfloor}\sum\limits_{d=1}^{min(n,m)}\sigma(d)[gcd(i,j)=1][\sigma(d)\le a]$

$=\sum\limits_{i=1}^{\lfloor\frac{n}{d} \rfloor}\sum\limits_{j=1}^{\lfloor\frac{m}{d} \rfloor}\sum\limits_{d=1}^{min(n,m)}\sigma(d)\sum\limits_{D|gcd(i,j)}\mu(D)[\sigma(d)\le a]$

$=\sum\limits_{i=1}^{\lfloor\frac{n}{d} \rfloor}\sum\limits_{j=1}^{\lfloor\frac{m}{d} \rfloor}\sum\limits_{d=1}^{min(n,m)}\sigma(d)\sum\limits_{D=1}^{min(\lfloor\frac{n}{d} \rfloor,\lfloor\frac{m}{d} \rfloor)}\mu(D)[D|gcd(i,j)][\sigma(d)\le a]$

$=\sum\limits_{Di=1}^{\lfloor\frac{n}{d} \rfloor}\sum\limits_{Dj=1}^{\lfloor\frac{m}{d} \rfloor}\sum\limits_{d=1}^{min(n,m)}\sigma(d)\sum\limits_{D=1}^{min(\lfloor\frac{n}{d} \rfloor,\lfloor\frac{m}{d} \rfloor)}\mu(D)[D|gcd(Di,Dj)][\sigma(d)\le a]$

$=\sum\limits_{i=1}^{\lfloor\frac{n}{Dd} \rfloor}\sum\limits_{j=1}^{\lfloor\frac{m}{Dd} \rfloor}\sum\limits_{d=1}^{min(n,m)}\sigma(d)\sum\limits_{D=1}^{min(\lfloor\frac{n}{d} \rfloor,\lfloor\frac{m}{d} \rfloor)}\mu(D)[\sigma(d)\le a]$

令$T=Dd$,則有 $\sum\limits_{i=1}^{\lfloor\frac{n}{T} \rfloor}\sum\limits_{j=1}^{\lfloor\frac{m}{T} \rfloor}\sum\limits_{d=1}^{min(n,m)}\sigma(d)\sum\limits_{T=1}^{min(n,m)}\mu(\frac{T}{d})[\sigma(d)\le a]=\sum\limits_{T=1}^{min(n,m)}\mu(\frac{T}{d})\sum\limits_{i=1}^{\lfloor\frac{n}{T} \rfloor}\sum\limits_{j=1}^{\lfloor\frac{m}{T} \rfloor}\sum\limits_{d=1}^{min(n,m)}\sigma(d)[\sigma(d)\le a]$

$=\sum\limits_{T=1}^{min(n,m)}{\lfloor\frac{n}{T} \rfloor}{\lfloor\frac{m}{T} \rfloor}\sum\limits_{d=1}^{min(n,m)}\sigma(d)\mu(\frac{T}{d})[\sigma(d)\le a]$

令$g(T)=\sum\limits_{d=1}^{min(n,m)}\sigma(d)\mu(\frac{T}{d})[\sigma(d)\le a]$

那麼有$\sum\limits_{T=1}^{min(n,m)}{\lfloor\frac{n}{T} \rfloor}{\lfloor\frac{m}{T} \rfloor}g(T)$ ,只有$\sigma(d)\le a$會對其產生貢獻

多組詢問,考慮將詢問的$a$從小到大排序處理。再將$\sigma$按大小排序,進一步最佳化。

然後使用樹狀陣列每次加入新的貢獻,再計算這個詢問的答案。

樹狀陣列最壞情況需要加入$nlogn$次,每次複雜度是$logn$ ,複雜度$O(nlog^2n)$。

總時間複雜度$O(nlog^2n+q(\sqrt{n}logn))$

檢視程式碼
 void init(int mod = MOD) {


    np[0] = np[1] = 1;mu[1] = 1;
    sigma[1] = 1;low[1] = 1;
    for (int i = 2;i < N;i++) {
        if (!np[i]) {
            p.push_back(i);
            mu[i] = -1;
            sigma[i] = i + 1;low[i] = i;
        }
        for (auto j : p) {
            if (i * j < N) {
                np[i * j] = 1;
                if (i % j == 0) {
                    low[i * j] = low[i] * j;
                    if (i == low[i]) sigma[i * j] = sigma[i] * j + 1;
                    else sigma[i * j] = sigma[i / low[i]] * sigma[low[i] * j];
                    break;
                }
                else {
                    low[i * j] = j;
                    mu[i * j] = -mu[i];
                    sigma[i * j] = sigma[i] * (j + 1);
                }
            }
            else {
                break;
            }
        }
    }
}


struct Q {
    int a, id;
    int n, m;
    bool friend operator<(Q a, Q b) {
        return a.a < b.a;
    }
};

void Solve(int TIME) {

    int q;cin >> q;
    vc<Q> qu(q + 1);
    for (int i = 1;i <= q;i++) {
        int n, m, a;cin >> n >> m >> a;
        qu[i] = { a,i,n,m };
    }
    BIT tr(1e5 + 10);
    vi ans(q + 1);
    vc<pi> Sigma(1e5 + 1);
    for (int i = 1;i <= 1e5;i++) {
        Sigma[i] = { sigma[i],i };
    }
    sort(Sigma.begin() + 1, Sigma.end());
    sort(qu.begin() + 1, qu.end());
    int now = 0;
    for (int i = 1;i <= q;i++) {
        auto [a, id, n, m] = qu[i];
        int lim = min(n, m);
        for (int j = now + 1;j <= 1e5;j++) {
            if (Sigma[j].first > a) {
                now = j - 1;
                break;
            }
            for (int k = 1;k * Sigma[j].second <= 1e5;k++) {
                tr.add(k * Sigma[j].second, Sigma[j].first * mu[k] % MOD);
            }
        }
        for (int l = 1, r;l <= lim;l = r + 1) {
            r = lim;
            if (n / l) r = min(r, n / (n / l));
            if (m / l) r = min(r, m / (m / l));
            ans[id] = (ans[id] + tr.query(l, r) * (n / l) % MOD * (m / l) % MOD) % MOD;
        }
    }
    for (int i = 1;i <= q;i++)   cout << (ans[i] % MOD + MOD) % MOD << endl;

}

5.P2398 - GCD SUM

知識點

莫比烏斯反演,整除分塊

題解

$\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}gcd(i,j)=\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}\sum\limits_{k=1}^{min(n,m)}k[gcd(i,j)=k]$

$=\sum\limits_{i=1}^{\lfloor\frac{n}{k}\rfloor}\sum\limits_{j=1}^{\lfloor \frac{m}{k}\rfloor}\sum\limits_{k=1}^{min(n,m)}k\sum\limits_{d|gcd(i,j)}\mu(d)=\sum\limits_{i=1}^{\lfloor\frac{n}{k}\rfloor}\sum\limits_{j=1}^{\lfloor \frac{m}{k}\rfloor}\sum\limits_{k=1}^{min(n,m)}k\sum\limits_{d=1}^{min(\lfloor \frac{n}{k}\rfloor,\lfloor \frac{m}{k}\rfloor)}\mu(d)[d|gcd(i,j)]$

$=\sum\limits_{i=1}^{\lfloor\frac{n}{dk}\rfloor}\sum\limits_{j=1}^{\lfloor \frac{m}{dk}\rfloor}\sum\limits_{k=1}^{min(n,m)}k\sum\limits_{d=1}^{min(\lfloor \frac{n}{k}\rfloor,\lfloor \frac{m}{k}\rfloor)}\mu(d)$

$=\sum\limits_{i=1}^{\lfloor\frac{n}{dk}\rfloor}\sum\limits_{j=1}^{\lfloor \frac{m}{dk}\rfloor}\sum\limits_{k=1}^{min(n,m)}k\sum\limits_{d=1}^{min(\lfloor \frac{n}{k}\rfloor,\lfloor \frac{m}{k}\rfloor)}\mu(d)=\sum\limits_{k=1}^{min(n,m)}k\sum\limits_{d=1}^{min(\lfloor \frac{n}{k}\rfloor,\lfloor \frac{m}{k}\rfloor)}\mu(d)\lfloor\frac{n}{dk}\rfloor\lfloor\frac{m}{dk}\rfloor$

$=\sum\limits_{T=1}^{min(n,m)}\mu(\frac{T}{k})\sum\limits_{k|T}k\lfloor\frac{n}{T}\rfloor\lfloor\frac{m}{T}\rfloor$

$=\sum\limits_{T=1}^{min(n,m)}\varphi(T)\lfloor\frac{n}{T}\rfloor\lfloor\frac{m}{T}\rfloor$ ,(由$\mu*id=\varphi$得出)

推到這裡才發現$n=m$,那麼直接就是$\sum\limits_{T=1}^{n}\varphi(T)(\lfloor\frac{n}{T}\rfloor)^2$

檢視程式碼
 void Solve(int TIME) {

    int n;cin >> n;
    int res = 0;
    for (int l = 1, r;l <= n;l = r + 1) {
        r = n;
        if (n / l) r = min(r, n / (n / l));
        res += (pre_phi[r] - pre_phi[l - 1]) * (n / l) * (n / l);
    }
    cout << res << endl;

}

6.AGC038C - LCMs

知識點

狄利克雷卷積

題意

$\sum\limits_{i=1}^{n}\sum\limits_{j=i+1}^{n}lcm(A_i,A_j)$

題解

$\sum\limits_{i=1}^{n}\sum\limits_{j=i+1}^{n}lcm(A_i,A_j)=\frac12(\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}lcm(A_i,A_j)-\sum\limits_{i=1}^{n}A_i)$

$=\frac12(\sum\limits_{i=1}^{N}\sum\limits_{j=1}^{N}cnt_i \cdot cnt_j\cdot lcm(i,j)-\sum\limits_{i=1}^{N}i\cdot cnt_i)$

考慮計算$res1=\sum\limits_{i=1}^{N}\sum\limits_{j=1}^{N}cnt_i \cdot cnt_j\cdot lcm(i,j)=\sum\limits_{i=1}^{N}\sum\limits_{j=1}^{N}cnt_i \cdot cnt_j\cdot \frac{ij}{gcd(i,j)}$

$=\sum\limits_{i=1}^{N}\sum\limits_{j=1}^{N}\sum\limits_{k=1}^{N}cnt_i \cdot cnt_j\cdot \frac{ij}{k}[gcd(i,j)=k]=\sum\limits_{i=1}^{\lfloor \frac{N}{k} \rfloor}\sum\limits_{j=1}^{\lfloor \frac{N}{k} \rfloor}\sum\limits_{k=1}^{N}cnt_{ki} \cdot cnt_{kj}\cdot \frac{ki\cdot kj}{k}[gcd(i,j)=1]$

$=\sum\limits_{i=1}^{\lfloor \frac{N}{k} \rfloor}\sum\limits_{j=1}^{\lfloor \frac{N}{k} \rfloor}\sum\limits_{k=1}^{N}cnt_{ki} \cdot cnt_{kj}\cdot kij\sum\limits_{d|gcd(i,j)}\mu(d)=\sum\limits_{i=1}^{\lfloor \frac{N}{k} \rfloor}\sum\limits_{j=1}^{\lfloor \frac{N}{k} \rfloor}\sum\limits_{k=1}^{N}cnt_{ki} \cdot cnt_{kj}\cdot kij\sum\limits_{d=1}^{\lfloor \frac{N}{k} \rfloor}\mu(d)[d|gcd(i,j)]$

$=\sum\limits_{i=1}^{\lfloor \frac{N}{dk} \rfloor}\sum\limits_{j=1}^{\lfloor \frac{N}{dk} \rfloor}\sum\limits_{k=1}^{N}cnt_{dki} \cdot cnt_{dkj}\cdot d^2kij\sum\limits_{d=1}^{\lfloor \frac{N}{k} \rfloor}\mu(d)$

令$T=dk$,則有$\sum\limits_{i=1}^{\lfloor \frac{N}{T} \rfloor}\sum\limits_{j=1}^{\lfloor \frac{N}{T} \rfloor}\sum\limits_{k|T}cnt_{Ti} \cdot cnt_{Tj}\cdot T\frac{T}{k}ij\sum\limits_{T=1}^{N}\mu(\frac{T}{k})$

$=\sum\limits_{T=1}^{N}T\sum\limits_{k|T}\mu(\frac{T}{k})\frac{T}{k}\sum\limits_{i=1}^{\lfloor \frac{N}{T} \rfloor}\sum\limits_{j=1}^{\lfloor \frac{N}{T} \rfloor}cnt_{Ti} \cdot cnt_{Tj}\cdot ij=\sum\limits_{T=1}^{N}T\sum\limits_{k|T}\mu(\frac{T}{k})\frac{T}{k}(\sum\limits_{i=1}^{\lfloor \frac{N}{T} \rfloor}cnt_{Ti} \cdot i)^2$

$=\sum\limits_{T=1}^{N}T\sum\limits_{k|T}\mu(k)k(\sum\limits_{i=1}^{\lfloor \frac{N}{T} \rfloor}cnt_{Ti} \cdot i)^2$

最後的答案$\frac12(\sum\limits_{T=1}^{N}T\sum\limits_{k|T}\mu(k)k(\sum\limits_{i=1}^{\lfloor \frac{N}{T} \rfloor}cnt_{Ti} \cdot i)^2-\sum\limits_{i=1}^{N}i\cdot cnt_i)$

檢視程式碼
 void Solve(int TIME) {

    int n;cin >> n;
    for (int i = 1;i <= n;i++) {
        int x;cin >> x;cnt[x] += 1;
    }
    for (int i = 1;i <= 1e6;i++) {
        for (int j = 1;j * i <= 1e6;j++) {
            f[i * j] = (f[i * j] + mu[i] * i % MOD) % MOD;
        }
    }

    int res = 0;
    for (int T = 1;T <= 1e6;T++) {
        int t = 0;
        for (int j = 1;j <= 1e6 / T;j++) {
            t = (t + cnt[T * j] * j % MOD) % MOD;
        }
        res = (res + T * f[T] % MOD * t % MOD * t % MOD) % MOD;
    }
    for (int i = 1;i <= 1e6;i++) {
        res = (res - 1ll * i * cnt[i] % MOD) % MOD;
    }
    res = res * inv(2) % MOD;
    cout << (res % MOD + MOD) % MOD << endl;

}

或者改成下一題用莫反第二形式,程式碼如下

檢視程式碼
 void Solve(int TIME) {

    int n;cin >> n;
    const int M = 1e6;
    vi cnt(M + 1);
    vi a(n + 1);for (int i = 1;i <= n;i++) cin >> a[i], cnt[a[i]] += 1;
    vi f(M + 1);
    for (int i = 1;i <= M;i++) {
        for (int j = i;j <= M;j += i) {
            f[i] = (f[i] + cnt[j] * j % MOD) % MOD;
        }
    }

    for (int i = 1;i <= M;i++) f[i] = f[i] * f[i] % MOD;

    int res = 0;
    for (int i = 1;i <= M;i++) {
        int t = 0;
        for (int j = 1;j * i <= M;j++) {
            t = (t + f[i * j] * mu[j] % MOD) % MOD;
        }
        res = (res + t * inv(i) % MOD) % MOD;
    }
    for (int i = 1;i <= n;i++) res = (res - a[i] + MOD) % MOD;
    cout << res * inv(2) % MOD << endl;

}

7.P3911 最小公倍數之和

知識點

莫比烏斯容斥

題意

$\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}lcm(a_i,a_j)$

題解

兩種方法,一種是將式子的$a_i$轉化為列舉值域然後記錄陣列$cnt_i$。

另一種方法是使用莫比烏斯反演的第二種形式。

上一題跟這個差不多,用的是法一。這裡用第二種方法寫一下。

$\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}lcm(a_i,a_j)=\sum\limits_{i=1}^{n}a_i\sum\limits_{j=1}^{n}a_j \frac{1}{gcd(a_i,a_j)}$

$=\sum\limits_{i=1}^{n}a_i\sum\limits_{j=1}^{n}a_j\sum\limits_{d=1}^{n} \frac{1}{d}[gcd(a_i,a_j)=d]$

$=\sum\limits_{i=1}^{n}a_i\sum\limits_{j=1}^{n}a_j\sum\limits_{d=1}^{n} \frac{1}{d}\sum\limits_{D=d,2d,...}[D|gcd(a_i,a_j)]\mu(\frac{D}{d})$

$=\sum\limits_{D=d,2d,...}\sum\limits_{i=1}^{n}a_i[D|a_i]\sum\limits_{j=1}^{n}a_j[D|a_j]\sum\limits_{d=1}^{n} \frac{1}{d}\mu(\frac{D}{d})$

$=\sum\limits_{D=d,2d,...}(\sum\limits_{i=1}^{n}a_i[D|a_i])^2\sum\limits_{d=1}^{n} \frac{1}{d}\mu(\frac{D}{d})$

令$F(D)=(\sum\limits_{i=1}^{n}a_i[D|a_i])^2$

原式$=\sum\limits_{d=1}^{n}\frac{1}{d} \sum\limits_{D=d,2d,...}F(D)\mu(\frac{D}{d})$

其中$F(x)$可以$O(nlogn)$預處理,而後面這個也可以$O(nlogn)$解決。

檢視程式碼
 void Solve(int TIME) {

    int n;cin >> n;
    const int M = 5e4;
    vi cnt(M + 1);
    vi a(n + 1);for (int i = 1;i <= n;i++) cin >> a[i], cnt[a[i]] += 1;
    vi f(M + 1);
    for (int i = 1;i <= M;i++) {
        for (int j = i;j <= M;j += i) {
            f[i] += cnt[j] * j;
        }
    }

    for (int i = 1;i <= M;i++) f[i] = f[i] * f[i];

    int res = 0;
    for (int i = 1;i <= M;i++) {
        int t = 0;
        for (int j = 1;j * i <= M;j++) {
            t += f[i * j] * mu[j];
        }
        res += t / i;
    }
    cout << res << endl;

}

8.HDU 7325 - GCD Magic

知識點

狄利克雷卷積,杜教篩

題意

​$\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}[\gcd(2^i-1,2^j-1)]^k$

題解

推公式或打表可以得出$gcd(2^{i}-1,2^{j}-1)=gcd(2^i-1,2^j-2^i)=gcd(2^i-1,2^i(2^{j-i}-1))$

$=gcd(2^i-1,2^{j-i}-1)=...=2^{gcd(i,j)}-1$

法1.

$\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}[\gcd(2^i-1,2^j-1)]^k=\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}(2^{gcd(i,j)}-1)^k$

$=\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}\sum\limits_{d=1}^{n}(2^{d}-1)^k[gcd(i,j)=d]=\sum\limits_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum\limits_{j=1}^{\lfloor\frac{n}{d}\rfloor}\sum\limits_{d=1}^{n}(2^{d}-1)^k[gcd(i,j)=1]$

$=\sum\limits_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum\limits_{j=1}^{\lfloor\frac{n}{d}\rfloor}\sum\limits_{d=1}^{n}(2^{d}-1)^k\sum\limits_{D|gcd(i,j)}\mu(D)=\sum\limits_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum\limits_{j=1}^{\lfloor\frac{n}{d}\rfloor}\sum\limits_{d=1}^{n}(2^{d}-1)^k\sum\limits_{D=1}^{\lfloor\frac{n}{d} \rfloor}\mu(D)[D|gcd(i,j)]$

$=\sum\limits_{i=1}^{\lfloor\frac{n}{dD}\rfloor}\sum\limits_{j=1}^{\lfloor\frac{n}{dD}\rfloor}\sum\limits_{d=1}^{n}(2^{d}-1)^k\sum\limits_{D=1}^{\lfloor\frac{n}{d} \rfloor}\mu(D)[D|gcd(Di,Dj)]$

令$T=dD$,那麼有$\sum\limits_{i=1}^{\lfloor\frac{n}{T}\rfloor}\sum\limits_{j=1}^{\lfloor\frac{n}{T}\rfloor}\sum\limits_{T=1}^{n}\sum\limits_{d|T}(2^{d}-1)^k\mu(\frac{T}{d})=\sum\limits_{T=1}^{n}(\lfloor\frac{n}{T}\rfloor)^2\sum\limits_{d|T}(2^{d}-1)^k\mu(\frac{T}{d})$

令$f(d)=(2^d-1)^k$,$g(T)=\sum\limits_{d|T}f(d)\mu(\frac{T}{d})$, 那麼有$\sum\limits_{T=1}^{n}(\lfloor\frac{n}{T}\rfloor)^2g(T)$

現在即求$g=f*\mu$的字首和$G$,考慮將$g$捲上$1$,則$\sum\limits_{i=1}^{n}f(i)=\sum\limits_{i=1}^{n}(g*1)(i)=\sum\limits_{i=1}^{n}\sum\limits_{j|i}g(j)=\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{\lfloor \frac{n}{i} \rfloor}g(j)=\sum\limits_{i=1}^{n}G(\lfloor \frac{n}{i} \rfloor)$

即有$G(n)=\sum\limits_{i=1}^{n}f(i)-\sum\limits_{i=2}^{n}G(\lfloor \frac{n}{i} \rfloor)$,這一步也就是杜教篩的結論。

然後計算$f$的字首和,$F(n)=\sum\limits_{i=1}^{n}(2^i-1)^k=\sum\limits_{i=1}^{n}\sum\limits_{j=0}^{k}C_{k}^{j}2^{ij}(-1)^{k-j}=\sum\limits_{j=0}^{k}C_{k}^{j}(-1)^{k-j}\sum\limits_{i=1}^{n}(2^{j})^i$

後面是個等比數列,然後可以每次$klogn$查詢$f$的字首和

預處理出$1e6$範圍的 $G$,然後杜教篩即可。複雜度$O(T(n^{\frac23}logn^\frac32+k\sqrt{n}logn))$

檢視程式碼
int g[N];

int sum_f(int n, int k) {
    int res = 0;
    for (int j = 0;j <= k;j++) {
        int sgn = (k - j) % 2 == 1 ? MOD - 1 : 1;
        int q = (1 << j);
        if (j == 0) {
            res = (res + n * sgn % MOD) % MOD;
        }
        else {
            res = add(res, sgn * comb(k, j) % MOD * (q * (qp(q, n) - 1) % MOD * inv(q - 1) % MOD) % MOD);
        }
    }
    return norm(res);
}

int Du_sum_g(int n, int pre_f[], umap<int, int>& mp, int k) {
    if (n < N) return pre_f[n];
    if (mp.count(n)) return mp[n];
    int res = sum_f(n, k);
    for (int l = 2, r;l <= n;l = r + 1) {
        r = n;
        if (n / l) r = min(r, n / (n / l));
        res = (res - Du_sum_g(n / l, pre_f, mp, k) * (r - l + 1) % MOD) % MOD;
    }
    return mp[n] = res;
}

void Solve(int TIME) {

    int n, k;cin >> n >> k;
    umap<int, int> mp;
    for (int i = 0;i <= 1e6;i++) g[i] = 0;
    for (int i = 1;i <= 1e6;i++) {
        int t = qp((qp(2, i) - 1), k);
        for (int j = 1;j * i <= 1e6;j++) {
            g[i * j] = (g[i * j] + mu[j] * t % MOD) % MOD;
        }
    }
    for (int i = 1;i <= 1e6;i++) {
        g[i] = (g[i - 1] + g[i]) % MOD;
    }

    int res = 0;
    for (int l = 1, r;l <= n;l = r + 1) {
        r = n;
        if (n / l) r = min(r, n / (n / l));
        res = add(res, (n / l) * (n / l) % MOD * (Du_sum_g(r, g, mp, k) - Du_sum_g(l - 1, g, mp, k)) % MOD);
    }
    cout << norm(res) << endl;

}

法2.

$\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}[\gcd(2^i-1,2^j-1)]^k=\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}(2^{gcd(i,j)}-1)^k$

$=\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}\sum\limits_{d=1}^{n}(2^{d}-1)^k[gcd(i,j)=d]=\sum\limits_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum\limits_{j=1}^{\lfloor\frac{n}{d}\rfloor}\sum\limits_{d=1}^{n}(2^{d}-1)^k[gcd(i,j)=1]$

$=\sum\limits_{d=1}^{n}(2^{d}-1)^k(\sum\limits_{i=1}^{\lfloor\frac{n}{d}\rfloor}2\varphi(i)-1)$

考慮對後面的$\lfloor\frac{n}{d}\rfloor$整除分塊,杜教篩處理後面的$\varphi$的字首和,前面的也可以透過上面所說的變成等比數列,每次$klogn$複雜度的查詢。

根據$\varphi*1=id$做杜教篩

$\sum\limits_{i=1}^{n}id(i)=\sum\limits_{i=1}^{n}\varphi(i)\sum\limits_{j=1}^{\lfloor \frac{n}{i} \rfloor}1=\sum\limits_{i=1}^{n}1\sum\limits_{j=1}^{\lfloor \frac{n}{i} \rfloor}\varphi(j)=\sum\limits_{i=1}^{n}\varphi(i)+\sum\limits_{i=2}^{n}\sum\limits_{j=1}^{\lfloor \frac{n}{i} \rfloor}\varphi(j)$

即$\sum\limits_{i=1}^{n}\varphi(i)=\sum\limits_{i=1}^{n}id(i)-\sum\limits_{i=2}^{n}\sum\limits_{j=1}^{\lfloor \frac{n}{i} \rfloor}\varphi(j)=\frac{n(n+1)}{2}-\sum\limits_{i=2}^{n}\sum\limits_{j=1}^{\lfloor \frac{n}{i} \rfloor}\varphi(j)$

檢視程式碼
 int g[N];

int sum_f(int n, int k) {
    int res = 0;
    for (int j = 0;j <= k;j++) {
        int sgn = (k - j) % 2 == 1 ? MOD - 1 : 1;
        int q = (1 << j);
        if (j == 0) {
            res = (res + n * sgn % MOD) % MOD;
        }
        else {
            res = add(res, sgn * comb(k, j) % MOD * (q * (qp(q, n) - 1) % MOD * inv(q - 1) % MOD) % MOD);
        }
    }
    return norm(res);
}


int Du_sum_phi(int n, int pre_f[], umap<int, int>& mp) {
    if (n < N) return pre_f[n];
    if (mp.count(n)) return mp[n];
    int res = n * (n + 1) / 2;
    for (int l = 2, r;l <= n;l = r + 1) {
        r = n;
        if (n / l) r = min(r, n / (n / l));
        res = (res - Du_sum_phi(n / l, pre_f, mp) * (r - l + 1) % MOD) % MOD;
    }
    return mp[n] = res;
}


umap<int, int> mp_sum_phi;
void Solve(int TIME) {

    int n, k;cin >> n >> k;
    int res = 0;
    for (int l = 1, r;l <= n;l = r + 1) {
        r = n;
        if (n / l) r = min(r, n / (n / l));
        res = add(res, (sum_f(r, k) - sum_f(l - 1, k)) % MOD * (2 * Du_sum_phi(n / l, pre_phi, mp_sum_phi) % MOD - 1) % MOD);
    }
    cout << norm(res) << endl;

}

9.[2021CCPC威海熱身賽] Number Theory

知識點

打表

題意

$\sum\limits_{k=1}^{n} \sum\limits_{i|k} \sum\limits_{j|i}\lambda(i)\lambda(j)$,其中$\lambda(x)=(-1)^{\sum\limits_{i=1}^{k}c_i}$,$x=\prod\limits_{i=1}^{k}p_i^{c_i}$ 。$1\le n\le 10^{12}$

題解

打表易知$\sum\limits_{d|n}\lambda(d)=[n=a^2,a\in N^+]$

$\sum\limits_{k=1}^{n} \sum\limits_{i|k} \sum\limits_{j|i}\lambda(i)\lambda(j)=\sum\limits_{k=1}^{n} \sum\limits_{i|k}\lambda(i) [i=a^2,a\in N^{+}]$

設$f(i)=\lambda(i) [i=a^2,a\in N^{+}]=[i=a^2,a\in N^{+}]$,那麼就有$\sum\limits_{k=1}^{n} \sum\limits_{i|k}f(i)=\sum\limits_{i=1}^{n}f(i)\lfloor \frac{n}{i} \rfloor$

這樣列舉平方數即可,複雜度$O(\sqrt{n})$

檢視程式碼
 void Solve(int TIME) {

    int n;cin >> n;int res = 0;
    for (int i = 1;i * i <= n;i++) {
        res = (res + n / (i * i) % MOD) % MOD;
    }
    cout << res << endl;

}

10.2019ICPC西安區域賽 D - Easy Problem

知識點

尤拉降冪,莫比烏斯反演

題解

$\sum\limits_{a_1=1}^{m}\sum\limits_{a_2=1}^{m}...\sum\limits_{a_n=1}^{m}(a_1a_2...a_n)^k[gcd(a_1,a_2,...,a_n)=d]$

$=d^{nk}\sum\limits_{a_1=1}^{\lfloor \frac{m}{d }\rfloor}\sum\limits_{a_2=1}^{\lfloor \frac{m}{d }\rfloor}...\sum\limits_{a_n=1}^{\lfloor \frac{m}{d }\rfloor}(a_1a_2...a_n)^k[gcd(da_1,da_2,...,da_n)=d]$

$=d^{nk}\sum\limits_{a_1=1}^{\lfloor \frac{m}{d }\rfloor}\sum\limits_{a_2=1}^{\lfloor \frac{m}{d }\rfloor}...\sum\limits_{a_n=1}^{\lfloor \frac{m}{d }\rfloor}(a_1a_2...a_n)^k[gcd(a_1,a_2,...,a_n)=1]$

$=d^{nk}\sum\limits_{a_1=1}^{\lfloor \frac{m}{d }\rfloor}\sum\limits_{a_2=1}^{\lfloor \frac{m}{d }\rfloor}...\sum\limits_{a_n=1}^{\lfloor \frac{m}{d }\rfloor}(a_1a_2...a_n)^k\sum\limits_{D|gcd(a_1,a_2,...,a_n)}\mu(D)$

$=d^{nk}\sum\limits_{a_1=1}^{\lfloor \frac{m}{d }\rfloor}\sum\limits_{a_2=1}^{\lfloor \frac{m}{d }\rfloor}...\sum\limits_{a_n=1}^{\lfloor \frac{m}{d }\rfloor}(a_1a_2...a_n)^k\sum\limits_{D=1}^{\lfloor \frac{m}{d }\rfloor}\mu(D)[D|gcd(a_1,a_2,...,a_n)]$

$=d^{nk}D^{nk}\sum\limits_{a_1=1}^{\lfloor \frac{m}{dD }\rfloor}\sum\limits_{a_2=1}^{\lfloor \frac{m}{dD }\rfloor}...\sum\limits_{a_n=1}^{\lfloor \frac{m}{dD }\rfloor}(a_1a_2...a_n)^k\sum\limits_{D=1}^{\lfloor \frac{m}{d }\rfloor}\mu(D)[D|gcd(Da_1,Da_2,...,Da_n)]$

$=(dD)^{nk}\sum\limits_{a_1=1}^{\lfloor \frac{m}{dD }\rfloor}\sum\limits_{a_2=1}^{\lfloor \frac{m}{d D}\rfloor}...\sum\limits_{a_n=1}^{\lfloor \frac{m}{dD }\rfloor}(a_1a_2...a_n)^k\sum\limits_{D=1}^{\lfloor \frac{m}{d }\rfloor}\mu(D)$

$=\sum\limits_{D=1}^{\lfloor \frac{m}{d }\rfloor}\mu(D)(dD)^{nk}\sum\limits_{a_1=1}^{\lfloor \frac{m}{d D}\rfloor}a_1^k\sum\limits_{a_2=1}^{\lfloor \frac{m}{d D}\rfloor}a_2^k...\sum\limits_{a_n=1}^{\lfloor \frac{m}{d D}\rfloor}a_n^k$

$=d^{nk}\sum\limits_{D=1}^{\lfloor \frac{m}{d }\rfloor}\mu(D)D^{nk}(\sum\limits_{i=1}^{\lfloor \frac{m}{d D}\rfloor}i^k)^n$

然後用尤拉降冪即可。

可以線性篩預處理$i^k$,與$i^k$的字首和,然後$O(mlog(mod))$掃過去。

檢視程式碼
 int k;

void init(int mod = MOD) {
    
    np[0] = np[1] = 1;mu[1] = 1;idk[1] = 1;
    for (int i = 2;i < N;i++) {
        if (!np[i]) {
            p.push_back(i);
            mu[i] = -1;
            idk[i] = qp(i, k);
        }
        for (auto j : p) {
            if (i * j < N) {
                np[i * j] = 1;
                if (i % j == 0) {
                    idk[i * j] = idk[i] * idk[j] % MOD;
                    break;
                }
                else {
                    mu[i * j] = -mu[i];
                    idk[i * j] = idk[i] * idk[j] % MOD;
                }
            }
            else {
                break;
            }
        }
    }
    for (int i = 1;i < N;i++) {
        sum_idk[i] = (idk[i] + sum_idk[i - 1]) % MOD;
    }
}

int phi_mod = getphi(MOD);

void Solve(int TIME) {

    string n;cin >> n;
    int m, d;cin >> m >> d >> k;
    init();
    int res = qp(d, k);
    int nn = exp_euler(n, MOD);
    res = qp(res, nn);
    int t = 0;
    for (int i = 1;i <= m / d;i++) {
        t = (t + mu[i] * qp(idk[i], nn) % MOD * qp(sum_idk[m / d / i], nn) % MOD) % MOD;
    }
    cout << norm(res * t) << endl;

}

11.CF1900D - Small GCD

知識點

尤拉反演,gcd的調和級數容斥,莫比烏斯容斥

題意

$\sum\limits_{i=1}^{n}\sum\limits_{j=i+1}^{n}gcd(a_i,a_j) \cdot(n-j)$

題解

考慮右端點遞增,然後思考如何去維護左側所有區間的資訊。

$f(t)$表示$gcd(a_i,a_j)=t$的對數

考慮容斥:$g(t)$表示$t|gcd(a_i,a_j)$的對數

$g(t)=f(t)+f(2t)+f(3t)+...$

那麼$f(t)=g(t)-f(2t)-f(3t)-...$

而$g(t)$可以透過預處理得到,對每個數的因子進行判斷記錄貢獻即可。

答案是$\sum\limits_{i=1}^{N}i\cdot f(i)$

檢視程式碼
 vvc<int> factor(N + 10);
 
 
void Solve(int TIME) {
 
    int T;cin >> T;
    for (int i = 1;i <= N;i++) {
        for (int j = 1;j * i <= N;j++) {
            factor[i * j].push_back(i);
        }
    }
    while (T--) {
        int n;cin >> n;
        vi a(n + 1);for (int i = 1;i <= n;i++) cin >> a[i];
        sort(a.begin() + 1, a.end());
        vi cnt(N + 10);
        vi f(N + 10);
        for (int i = 1;i <= n;i++) {
            for (auto x : factor[a[i]]) {
                f[x] += cnt[x] * (n - i);
                cnt[x] += 1;
            }
        }
        vi F(N + 10);
        int res = 0;
        for (int i = N;i >= 1;i--) {
            F[i] = f[i];
            for (int j = i + i;j <= N;j += i) {
                F[i] -= F[j];
            }
            res += F[i] * i;
        }
 
        cout << res << endl;
    }
 
}

法2.

利用尤拉反演:$gcd(a_i,a_j)=\sum\limits_{d|gcd(a_i,a_j)}\varphi(d)$

$\sum\limits_{i=1}^{j-1}\sum\limits_{j=1}^{n}gcd(a_i,a_j) \cdot (n-j)=\sum\limits_{i=1}^{j-1}[d|a_i]\sum\limits_{j=1}^{n}[d|a_j](n-j)\sum\limits_{d=1}^{n}\varphi(d) $

$=\sum\limits_{d=1}^{N}\sum\limits_{j=1}^{n}[d|a_j](n-j)\sum\limits_{i=1}^{j-1}[d|a_i]$

列舉右端點,列舉當前$a_j$的約數記錄貢獻即可。

檢視程式碼
 void Solve(int TIME) {

    int T;cin >> T;
    vvc<int> factor(N + 10);
    for (int i = 1;i <= N;i++) {
        for (int j = 1;j * i <= N;j++) {
            factor[i * j].push_back(i);
        }
    }

    while (T--) {
        int n;cin >> n;
        vi a(n + 1);for (int i = 1;i <= n;i++) cin >> a[i];
        sort(a.begin() + 1, a.end());
        int res = 0;
        vi cnt(N + 10);
        for (int i = 1;i <= n;i++) {
            for (auto x : factor[a[i]]) {
                res += cnt[x] * (n - i) * phi[x];
                cnt[x] += 1;
            }
        }
        cout << res << endl;
    }

}

法3.利用莫比烏斯反演的倍數形式

$\sum\limits_{i=1}^{n}\sum\limits_{j=i+1}^{n}\sum\limits_{x=1}^{N}x[gcd(a_i,a_j)=x]\cdot(n-j)$

$=\sum\limits_{i=1}^{n}\sum\limits_{j=i+1}^{n}\sum\limits_{x=1}^{N}x\sum\limits_{d=x,2x,...}[d|gcd(a_i,a_j)]\mu(\frac{d}{x})\cdot(n-j)$

$=\sum\limits_{x=1}^{N}x\sum\limits_{d=x,2x,...}\sum\limits_{i=1}^{n}[d|a_i]\sum\limits_{j=i+1}^{n}[d|a_j]\cdot(n-j)\mu(\frac{d}{x})$

令$f(d)=\sum\limits_{i=1}^{n}[d|a_i]\sum\limits_{j=i+1}^{n}[d|a_j]\cdot(n-j)$

原式$=\sum\limits_{x=1}^{N}x\sum\limits_{d=x,2x,...}f(d)\mu(\frac{d}{x})$

檢視程式碼
 vvc<int> factor(N + 10);


void Solve(int TIME) {

    int T;cin >> T;
    for (int i = 1;i <= N;i++) {
        for (int j = 1;j * i <= N;j++) {
            factor[i * j].push_back(i);
        }
    }

    while (T--) {
        int n;cin >> n;
        vi a(n + 1);for (int i = 1;i <= n;i++) cin >> a[i];
        sort(a.begin() + 1, a.end());
        int res = 0;
        vi cnt(N + 10);
        vi f(N + 10);
        for (int i = 1;i <= n;i++) {
            for (auto x : factor[a[i]]) {
                f[x] += cnt[x] * (n - i);
                cnt[x] += 1;
            }
        }
        for (int i = 1;i <= N;i++) {
            for (int j = 1;i * j <= N;j++) {
                res += i * f[i * j] * mu[j];
            }
        }
        cout << res << endl;
    }

}

12.I-hx的數列

知識點

數論分塊,莫比烏斯反演

題解

$(a,a\cdot \frac{c}{b},a\cdot{(\frac{c}{b})}^2)$,令$t=a\cdot (\frac{c}{b})^2$,需要$t\le n$

保證$gcd(b,c)=1$且$b<c$,於是就有$b^2|a$,這樣才能有$t$為整數

於是令$k=\frac{a}{b^2}$,則有$k\cdot c^2 \le n$,於是有$k\le \lfloor\frac{n}{c^2}\rfloor$

$\sum\limits_{c=1}^{\lfloor\sqrt{n}\rfloor}\lfloor\frac{n}{c^2}\rfloor \sum\limits_{b=1}^{c-1}[\gcd(b,c)=1]=\sum\limits_{c=1}^{\lfloor\sqrt{n}\rfloor}\lfloor\frac{n}{c^2}\rfloor \sum\limits_{b=1}^{c-1}\sum\limits_{D|gcd(b,c)}\mu(D)$

$=\sum\limits_{Dc=1}^{\lfloor\sqrt{n}\rfloor}\lfloor\frac{n}{D^2c^2}\rfloor \sum\limits_{Db=1}^{Dc-1}\sum\limits_{D=1}^{\lfloor\sqrt{n}\rfloor}\mu(D)[D|gcd(Db,Dc)]$

$=\sum\limits_{Dc=1}^{\lfloor\sqrt{n}\rfloor}\lfloor\frac{n}{D^2c^2}\rfloor \sum\limits_{Db=1}^{Dc-1}\sum\limits_{D=1}^{\lfloor\sqrt{n}\rfloor}\mu(D)=\sum\limits_{D=1}^{\lfloor\sqrt{n}\rfloor}\mu(D)\sum\limits_{c=1}^{\lfloor\frac{\lfloor\sqrt{n}\rfloor}{D}\rfloor}\lfloor\frac{n}{D^2c^2}\rfloor \sum\limits_{b=1}^{c-1}1$

$=\sum\limits_{D=1}^{\lfloor\sqrt{n}\rfloor}\mu(D)\sum\limits_{c=1}^{\lfloor\frac{\lfloor\sqrt{n}\rfloor}{D}\rfloor}\lfloor\frac{n}{D^2c^2}\rfloor (c-1)$

複雜度$O(\sqrt1+\sqrt2+...+\sqrt{\sqrt{n}})$

大抵是不會很大的。

檢視程式碼
 void Solve(int TIME) {

    int n;cin >> n;
    int res = 0;
    int sqn = sqrt(n);
    for (int i = 1;i <= sqn - 1;i++) {
        int t = 0;
        for (int l = 1, r;l <= sqn / i;l = r + 1) {
            int m = n / i / i;
            r = sqn / i;
            if (m / (l * l)) r = min((int)sqrt(m / (m / (l * l))), r);
            t += m / (l * l) * (l + r - 2) * (r - l + 1) / 2;
        }
        res += t * mu[i];
    }
    cout << res << endl;

}

13.牢大與GCD

知識點

莫比烏斯容斥

題意

給出長度為$n$的陣列$a$和$num$,求下面的式子。

$\sum\limits_{x=1}^{n}\sum\limits_{k_1=1}^{n}\sum\limits_{k_2=1}^{n}...\sum\limits_{k_{num[x]}=1}^{n}[\gcd\limits_{i=1}^{num[x]}(a_{k_i})=x]$

題解

$\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}\sum\limits_{k=1}^{n}[gcd(a_i,a_j,a_k)=x]$

$=\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}\sum\limits_{k=1}^{n}\sum\limits_{d=x,2x,...}[d|gcd(a_i,a_j,a_k)]\mu(\frac{d}{x})$

$=\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}\sum\limits_{k=1}^{n}\sum\limits_{d=x,2x,...}[d|a_i][d|a_j][d|a_k]\mu(\frac{d}{x})$

$=\sum\limits_{d=x,2x,...}\mu(\frac{d}{x})\sum\limits_{i=1}^{n}[d|a_i]\sum\limits_{j=1}^{n}[d|a_j]\sum\limits_{k=1}^{n}[d|a_k]$

$=\sum\limits_{d=x,2x,...}\mu(\frac{d}{x})(\sum\limits_{i=1}^{n}[d|a_i])^3$

可以看出,當需要gcd的個數擴大至$X$,變的僅僅是後面的冪次。

於是令$f(d)=\sum\limits_{i=1}^{n}[d|a_i]$

結果為$\sum\limits_{x=1}^{n}\sum\limits_{d=x,2x,...}\mu(\frac{d}{x})f(d)^{pw[x]}$

檢視程式碼
void Solve(int TIME) {

    int n;cin >> n;
    vi a(n + 1);for (int i = 1;i <= n;i++) cin >> a[i];
    int M = 1e5;
    vi cnt(M + 1);for (int i = 1;i <= n;i++) cnt[a[i]] += 1;

    //正解
    vi pw(n + 1);for (int i = 1;i <= n;i++) cin >> pw[i];
    vi f(M + 1);
    for (int i = 1;i <= M;i++) {
        for (int j = 1;j * i <= M;j++) {
            f[i] += cnt[i * j];
        }
    }
    int res = 0;
    for (int i = 1;i <= n;i++) {
        for (int j = 1;j * i <= M;j++) {
            res += mu[j] * qp(f[i * j], pw[i]);
        }
    }
    cout << res << endl;

    //暴力對拍
    // int res = 0;
    // for (int i = 1;i <= n;i++) {
    //     map<int, int> mp;mp[0] = 1;
    //     for (int j = 1;j <= pw[i];j++) {
    //         map<int,int> nmp;
    //         for (int k = 1;k <= n;k++) {
    //             for (auto [o1, o2] : mp) {
    //                 nmp[gcd(o1, a[k])] += o2;
    //             }
    //         }
    //         mp = nmp;
    //     }
    //     res += mp[i];
    // }
    // cout << res << endl;

}

14.G-小苯的逆序對_牛客小白月賽87 (nowcoder.com)

知識點

莫比烏斯容斥及其dp方法

題解

令$f(x)$表示$gcd(a_i,a_j)=x$並且$i和j$是逆序對的對數。

令$g(x)=f(x)+f(2x)+f(3x)...$,那麼$f(x)=g(x)-(f(2x)+f(3x)+...)$ 。這裡可以用dp做。

$f(x)=g(x)\mu(1)+g(2x)\mu(2)+...+g(kx)\mu(k)+...$ ,這裡轉換成莫比烏斯容斥。

$g(x)$是好算的,樹狀陣列維護即可,做完了。

dp:

檢視程式碼
 void Solve(int TIME) {
 
    int n;cin >> n;
    vi a(n + 1);
    vi pos(n + 1);
    for (int i = 1;i <= n;i++) cin >> a[i], pos[a[i]] = i;
    BIT tr(n + 1);
    vi f(n + 1);
    for (int i = n;i >= 1;i--) {
        for (int j = i;j <= n;j += i) {
            f[i] += tr.query(pos[j], n);
            tr.add(pos[j], 1);
        }
        for (int j = i;j <= n;j += i) {
            tr.add(pos[j], -1);
        }
        for (int j = 2 * i;j <= n;j += i) {
            f[i] -= f[j];
        }
    }
    cout << f[1] << endl;
 
}

莫比烏斯容斥:

檢視程式碼
 void Solve(int TIME) {
 
    int n;cin >> n;
    vi a(n + 1);
    vi pos(n + 1);
    for (int i = 1;i <= n;i++) cin >> a[i], pos[a[i]] = i;
    BIT tr(n + 1);
    int res = 0;
    for (int i = 1;i <= n;i++) {
        int t = 0;
        for (int j = i;j <= n;j += i) {
            t += tr.query(pos[j], n);
            tr.add(pos[j], 1);
        }
        for (int j = i;j <= n;j += i) {
            tr.add(pos[j], -1);
        }
        res += t * mu[i];
    }
    cout << res << endl;
 
}

15.P2398 GCD SUM

知識點

莫比烏斯反演

題解

莫比烏斯反演

$\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}gcd(i,j)=\sum\limits_{d=1}^{n}d\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}[gcd(i,j)=d]=\sum\limits_{d=1}^{n}d\sum\limits_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum\limits_{j=1}^{\lfloor\frac{n}{d}\rfloor}[gcd(di,dj)=d]$

$=\sum\limits_{d=1}^{n}d\sum\limits_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum\limits_{j=1}^{\lfloor\frac{n}{d}\rfloor}[gcd(i,j)=1]=\sum\limits_{d=1}^{n}d\sum\limits_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum\limits_{j=1}^{\lfloor\frac{n}{d}\rfloor}\sum\limits_{D|gcd(i,j)}\mu(D)$

$=\sum\limits_{d=1}^{n}d\sum\limits_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum\limits_{j=1}^{\lfloor\frac{n}{d}\rfloor}\sum\limits_{D=1}^{\lfloor\frac{n}{d}\rfloor}\mu(D)[D|gcd(i,j)]=\sum\limits_{d=1}^{n}d\sum\limits_{D=1}^{\lfloor\frac{n}{d}\rfloor}\mu(D)\sum\limits_{i=1}^{\lfloor\frac{n}{d}\rfloor}[D|i]\sum\limits_{j=1}^{\lfloor\frac{n}{d}\rfloor}[D|j]$

$=\sum\limits_{d=1}^{n}d\sum\limits_{D=1}^{\lfloor\frac{n}{d}\rfloor}\mu(D)(\lfloor\frac{n}{Dd}\rfloor)^2$

檢視程式碼
 void Solve(int TIME) {

    int n;cin >> n;
    int res = 0;
    for (int i = 1;i <= n;i++) {
        int m = n / i;
        for (int l = 1, r;l <= m;l = r + 1) {
            r = m;
            if (m / l) r = min(m / (m / l), r);
            res += (pre_mu[r] - pre_mu[l - 1]) * i * (m / l) * (m / l);
        }
    }
    cout << res << endl;

}

莫比烏斯容斥

$\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}gcd(i,j)=\sum\limits_{d=1}^{n}d\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}[gcd(i,j)=d]$

令$g(d)=\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}[gcd(i,j)=d]+\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}[gcd(i,j)=2d]+...$

得到$\sum\limits_{d=1}^{n}d\sum\limits_{k=1}^{n}\mu(k)g(kd)$

$g(x)$很好算,即每個$x$的倍數都可以選,選兩個可以重複。即$(\lfloor\frac{n}{x} \rfloor)^2$

檢視程式碼
 void Solve(int TIME) {

    int n;cin >> n;
    vi g(n + 1);
    for (int i = 1;i <= n;i++) {
        g[i] = (n / i) * (n / i);
    }
    int res = 0;
    for (int i = 1;i <= n;i++) {
        for (int j = 1;j * i <= n;j++) {
            res += i * mu[j] * g[j * i];
        }
    }
    cout << res << endl;

}

數論與雜項

1. 51nod1236 - 序列求和 V3

知識點

特徵方程,二次剩餘

題解

$F[n]=F[n-1]+F[n-2]$ ,由特徵方程 $q^2=q+1$,得到特徵根$q_1=\frac{1+\sqrt5}{2}$ ,$q_2=\frac{1-\sqrt5}{2}$

則有$F[0]=k_1q_1^0+k_2q_2^0=0$ ,$F[1]=k_1q_1^1+k_2q_2^1=1$,

解得$k_1=\frac{1}{\sqrt5}$ , $k_2=-\frac{1}{\sqrt5}$ , 得到 $F[n]=\frac{1}{\sqrt5}(\frac{1+\sqrt5}{2})^n-\frac{1}{\sqrt5}(\frac{1-\sqrt5}{2})^n=\frac{1}{\sqrt5}[(\frac{1+\sqrt5}{2})^n-(\frac{1-\sqrt5}{2})^n]$

$F[n]^k=(\frac{1}{\sqrt5})^k[(\frac{1+\sqrt5}{2})^n-(\frac{1-\sqrt5}{2})^n]^k =(\frac{1}{\sqrt5})^k\sum\limits_{i=0}^{k}C_{k}^{i}(\frac{1+\sqrt5}{2})^{ni}(\frac{1-\sqrt5}{2})^{n(k-i)}(-1)^{k-i}$

$S(n,k)=\sum\limits_{i=1}^{n}F[i]^k=\sum\limits_{i=1}^{n}(\frac{1}{\sqrt5})^k\sum\limits_{j=0}^{k}C_{k}^{j}(\frac{1+\sqrt5}{2})^{ij}(\frac{1-\sqrt5}{2})^{i(k-j)}(-1)^{k-j}$

$=(\frac{1}{\sqrt5})^k\sum\limits_{i=1}^{n}\sum\limits_{j=0}^{k}C_{k}^{j}[(\frac{1+\sqrt5}{2})^{j}(\frac{1-\sqrt5}{2})^{(k-j)}]^i(-1)^{k-j}$

$=(\frac{1}{\sqrt5})^k\sum\limits_{j=0}^{k}(-1)^{k-j}C_{k}^{j}\sum\limits_{i=1}^{n}[(\frac{1+\sqrt5}{2})^{j}(\frac{1-\sqrt5}{2})^{(k-j)}]^i$

後面的迴圈是個等比數列,快速冪處理即可,總時間複雜度$O(klogn)$

$\sqrt5$在模意義下的值用二次剩餘得到 ,可以直接打表找或使用Cipolla演算法。在本題中可以得到 383008016是5的一個二次剩餘。

檢視程式碼
void Solve(int TIME) {

    int n, k;cin >> n >> k;
    int res = 0;
    int a = (1 + sqrt5) % MOD * inv(2) % MOD;
    int b = (MOD + 1 - sqrt5) % MOD * inv(2) % MOD;
    for (int j = 0;j <= k;j++) {
        int t = qp(a, j) * qp(b, k - j) % MOD;
        int tt;
        if (t != 1) tt = t * (qp(t, n) - 1) % MOD * inv(t - 1) % MOD;
        else tt = n % MOD;
        if ((k - j) & 1) {
            res = sub(res, mul(comb(k, j), tt));
        }
        else {
            res = add(res, mul(comb(k, j), tt));
        }
    }
    res = mul(res, qp(inv(sqrt5), k));
    cout << norm(res) << endl;

}

2.CF1891D - Suspicious logarithms

知識點

數論分塊

題解

$2^{f(x)}\le x$,則有$f(x)=\lfloor log_2x \rfloor$

$f(x)^{g(x)}\le x$,則有$g(x)\cdot log_2f(x)\le log_2x$,即$g(x)\cdot log_2{\lfloor log_2x \rfloor}\le log_2x$,即$g(x)=\lfloor \frac{log_2x}{log_2{\lfloor log_2x \rfloor}} \rfloor$,即求$\sum\limits_{i=l}^{r}g(i)$

對於相等的$\lfloor log_2x \rfloor$,當$x$增大,$g(x)$也增大。而$\lfloor log_2x \rfloor$的範圍比較小,考慮列舉。之後在該範圍,將其劃分為多個不同的連續相同數字子段。

注意:$log_2(x)$這類函式預設使用$double$型別,使用前要強制轉換為$long\; double$,否則會爆。

檢視程式碼
 int f(int x) {
    return 63 - __builtin_clzll(x);
}
int g(int x) {
    return (ll)(log2(ld(x)) / log2((ld)f(x)));
}

struct qwq {
    int l, r, v;
};

void Solve(int TIME) {

    vc<qwq> a;int L = 4, R = 1E18;
    for (int l = L, r;l <= R;l = r + 1) {
        int fx = f(l);int lim = min((1ll << (fx + 1)) - 1, R);
        int lo = l + 1, hi = lim;
        int gx = g(l);
        while (lo <= hi) {
            int mid = (lo + hi) >> 1;
            if (g(mid) <= gx) {
                lo = mid + 1;
            }
            else {
                hi = mid - 1;
            }
        }
        r = lo - 1;
        a.push_back({ l,r,gx });
    }

    int q;cin >> q;
    while (q--) {
        cin >> L >> R;
        int res = 0;
        auto pt = partition_point(a.begin(), a.end(), [&](qwq A) {return A.r < L;}) - a.begin();
        for (int i = pt;i < a.size();i++) {
            auto [l, r, v] = a[i];
            if (l > R) break;
            else {
                l = max(l, L), r = min(r, R);
                res = (res + (r - l + 1) * v % MOD) % MOD;
            }
        }
        cout << res << endl;
    }
}

3.CFgym104076 E - Identical Parity

知識點

exgcd

題解

發現最後$1,1+k,1+2k...$ , $2,2+k,2+2k...$, ... $k-1,k-1+k,k-1+2k,....$這些都是同奇偶性。

也就是分成$k$組,其中$n \;mod \;k$組是$\lfloor\frac{n}{k}\rfloor+1$個元素,剩下$k-n \;mod\;k$組是$\lfloor\frac{n}{k}\rfloor$個元素。令$a=\lfloor\frac{n}{k}\rfloor$

然後要使得$ax+(a+1)y=n/2$有解$x,y$使得$0\le x\le (n \;mod\;k)$ 並且$0\le y \le (k-n \;mod\;k) $

二分求出每個邊界的可行範圍看有無交集即可。

檢視程式碼
 void Solve(int TIME) {

    int n, k;cin >> n >> k;
    int a = n / k;//人數
    int t = n % k;//人數為a+1的組數t,人數為a的組數k-t
    int x, y;
    int d = exgcd(a, a + 1, x, y);
    int m = n / 2;
    if (m % d) return NO, void();
    int base = m / d;
    x *= base, y *= base;
    int norm_1 = a + 1;
    int norm_2 = a;

    int l, r;
    l = -1e9, r = 1e9;
    while (l <= r) {
        int mid = l + r >> 1;
        if (x + mid * (a + 1) <= k - t) l = mid + 1;
        else r = mid - 1;
    }
    int r_a = l - 1;

    l = -1e9, r = 1e9;
    while (l <= r) {
        int mid = l + r >> 1;
        if (x + mid * (a + 1) >= 0) r = mid - 1;
        else l = mid + 1;
    }
    int l_a = r + 1;

    l = -1e9, r = 1e9;
    while (l <= r) {
        int mid = l + r >> 1;
        if (y - mid * (a) <= t) r = mid - 1;
        else l = mid + 1;
    }
    int l_b = r + 1;

    l = -1e9, r = 1e9;
    while (l <= r) {
        int mid = l + r >> 1;
        if (y - mid * (a) >= 0) l = mid + 1;
        else r = mid - 1;
    }
    int r_b = l - 1;

    if (min(r_a, r_b) >= max(l_a, l_b)) YES;
    else NO;

}

4.2023ICPC區域賽南京站C

知識點

異或的性質

題意

求滿足 $ i \text{^} (P-1)\equiv 1 (mod\; P)$ 並且$0\le i\le m$的$i$的個數

範圍$1\le m \le 10^{18} $,$1\le P \le 10^{18}$

題解

即 $i\text{^} (P-1)= kP+1$ ,$(k\ge 0)$ ,即$i=(kP+1) \text{^} (P-1)\le m$的數量

我們知道異或的性質$a-b \le $ $a \text{^} b$ $\le a+b$

所以$(k-1)P+2\le(kP+1) \text{^} (P-1) \le(k+1)P$

當$k\ge \lceil\frac{m}{p}\rceil+1$ ,$(kP+1) \text{^} (P-1) \ge(k-1)P+2 > m$

當$0\le k\le \lfloor\frac{m}{p}\rfloor-1$ ,$(kP+1) \text{^} (P-1) \le(k+1)P \le m$

所以只需檢查$\lfloor\frac{m}{p}\rfloor \le k\le \lceil\frac{m}{p} \rceil$範圍的$k$是否滿足$(kP+1)\text{^} (P-1)\le m$即可

檢視程式碼
 void Solve(int TIME) {

    int P, m;cin >> P >> m;
    int res = m / P;
    for (int i = m / P;i <= (m + P - 1) / P;i++) {
        int t = (i * P + 1) ^ (P - 1);
        if (t <= m) res += 1;
    }
    cout << res << endl;

}

好像不需要卡這麼死,知道大致上界後往後列舉個10位就行了。

檢視程式碼
 void Solve(int TIME) {

    const __int128 ONE = 1;
    int p, m;cin >> p >> m;
    int res = m / p;
    int n = m / p - 1;
    for (int i = n + 1;i <= n + 10;i++) {
        __int128 t = ONE * (ONE * i * p + 1) ^ (ONE * p - 1);
        if (t <= m) res += 1;
    }
    cout << res << endl;

}

解2

根據上面的式子,將$m$拆位列舉,列舉公共字首,在$1$的位置替換為$0$,然後後面的可以為任意。之後用類似字首和的方式算一下答案。

檢視程式碼
 void Solve(int TIME) {

    int p, m;cin >> p >> m;
    int mask = 0;
    int res = 0;
    auto calc = [&](int x) {//[0,x]中滿足kp+1=x的個數
        x--;
        if (x < 0) return 0ll;
        return x / p + 1;
        };
    auto calc2 = [&](int l, int r) {//[l,r]中滿足
        return calc(r) - calc(l - 1);
        };
    for (int i = 60;i >= 0;i--) {
        if (~m >> i & 1) continue;
        int t = mask ^ (p - 1);
        int l = t >> i << i, r = l + (1ll << i) - 1;
        res += calc2(l, r);
        mask += 1ll << i;
    }
    res += calc2(mask ^ (p - 1), mask ^ (p - 1));
    cout << res << endl;

}

5.2019ICPC西安站 F - Function!

知識點

數論分塊

題意

$\sum\limits_{a=2}^{n}(a\sum\limits_{b=a}^{n}\lfloor log_ab \rfloor)$

題解

$\sum\limits_{a=2}^{n}(a\sum\limits_{b=a}^{n}\lfloor log_ab \rfloor \lceil log_ba\rceil)=\sum\limits_{a=2}^{n}(a\sum\limits_{b=a}^{n}\lfloor log_ab \rfloor)$

發現當$a\cdot a > n$,$\lfloor log_ab \rfloor =1$ ,此時有$\sum\limits_{a=\lfloor\sqrt{n}\rfloor+1}^{n}a(n-a+1)=\sum\limits_{a=\lfloor\sqrt{n}\rfloor+1}^{n}\left(a(n+1)-a^2\right)$

於是在$a\cdot a \le n$時,分塊計算,$\sum\limits_{a=2}^{\lfloor \sqrt{n} \rfloor}\sum\limits_{b=a}^{n}\lfloor log_ab \rfloor$

複雜度$O(log1+log2+...+log\sqrt{n})=O(log{\frac{(1+\sqrt{n})\sqrt{n}}{2}})=O(logn)$

檢視程式碼
 int inv6 = inv(6);
int inv2 = inv(2);

int calc_n(int x) {
    x %= MOD;
    return (1 + x) % MOD * x % MOD * inv2 % MOD;
}
int calc_n_2(int x) {
    x %= MOD;
    return (2 * x + 1) % MOD * (x + 1) % MOD * x % MOD * inv6 % MOD;
}

void Solve(int TIME) {

    int n;cin >> n;
    int res = 0;
    int a = 2;
    for (a = 2;a * a <= n;a++) {
        for (int l = a, r, k = 1;l <= n;l = r + 1, k++) {
            r = min(l * a - 1, n);
            res = (res + (r - l + 1) % MOD * k % MOD * a % MOD) % MOD;
        }
    }
    res = (res + (n + 1) % MOD * (calc_n(n) - calc_n(a - 1)) % MOD) % MOD;
    res = (res - (calc_n_2(n) - calc_n_2(a - 1)) % MOD) % MOD;
    res = norm(res);
    cout << res << endl;

}

6.P5110 塊速遞推

知識點

二次剩餘,特徵方程,光速冪

題解

$a_n=233a_{n-1}+666a_{n-2}$ ,特徵方程$q^2=233q+666$

得到$\begin{cases} q_1=\frac{233+\sqrt{56953}}{2} \\q_2= \frac{233-\sqrt{56953}}{2} \end{cases}$

通項$a_n=k_1\cdot q_1^n+k_2\cdot q_2^n$,帶入$a_0=k_1+k_2=0$ ,$a_1=k_1\cdot q_1+k_2\cdot q_2=1$

得到$k_1=\frac{1}{q_1-q_2}=\frac{1}{\sqrt{56953}}$,$k_2=-\frac{1}{\sqrt{56953}}$。則有$a_n=\frac {(\frac{233+\sqrt{56953}}{2})^n-(\frac{233-\sqrt{56953}}{2})^n}{\sqrt{56953}}$

發現$\sqrt{56953}$在$10^9+7$下的二次剩餘為$188305837$,那麼直接帶入進去即可。

$a_n=\frac {(\frac{233+188305837}{2})^n-(\frac{233-188305837}{2})^n}{\sqrt{56953}}=\frac {94153035^n-(-94152802)^n}{188305837}=\frac {94153035^n-905847205^n}{188305837}(mod \;10^9+7) $

$=233230706\cdot(94153035^n-905847205^n)$

然後光速冪$O(\sqrt{MOD})$預處理,$O(1)$查詢即可。總複雜度$O(T+\sqrt{MOD})$

檢視程式碼
 struct LP {//光速冪,處理同底數同模數的冪
    int base1[N], basesqrt[N];
    int Block_len;
    int Phi;
    ll maxn = 1e10;//模數的最大值
    LP(int x) { init(x); }
    void init(int x) {//初始化底數為x
        Phi = MOD - 1;
        Block_len = sqrt(maxn) + 1;
        base1[0] = 1;for (int i = 1;i <= Block_len;i++) base1[i] = 1ll * base1[i - 1] * x % MOD;
        basesqrt[0] = 1;for (int i = 1;i <= Block_len;i++) basesqrt[i] = 1ll * basesqrt[i - 1] * base1[Block_len] % MOD;
    }
    int qp(ull x) {
        x %= Phi;
        return 1ll * basesqrt[x / Block_len] * base1[x % Block_len] % MOD;
    }
};

namespace Mker
{
    unsigned long long SA, SB, SC;
    void init() { cin >> SA >> SB >> SC; }
    unsigned long long rand()
    {
        SA ^= SA << 32, SA ^= SA >> 13, SA ^= SA << 1;
        unsigned long long t = SA;
        SA = SB, SB = SC, SC ^= t ^ SA;return SC;
    }
}

void Solve(int TIME) {

    int T;cin >> T;
    Mker::init();
    LP a(94153035);
    LP b(905847205);
    int res = 0;

    while (T--) {
        int n = Mker::rand();
        res ^= 233230706 * (a.qp(n) - b.qp(n) + MOD) % MOD;
    }
    cout << res << endl;

}

7.abc331D - Tile Pattern

知識點

容斥

題解

考慮對一個右下角$(x,y)$計算它到$(1,1)$的$1$的個數。

發現可以分為四個部分來求:

1.左上角被$(x/n)\cdot (y/n)$個$n\cdot n$覆蓋的正方形區域;

2.左下角被$(y/n)$個$(x \mod n )\cdot n$覆蓋的矩形區域

3.右上角被$(x/n)$個$x\cdot (y\mod n)$覆蓋的矩形區域

4.右下角被$1$個$(x\mod n)\cdot (y\mod n)$覆蓋的矩形區域

畫圖大點的圖手動模擬一下可以容易得出這個結論,我用了$8\cdot 7$的矩形,$n=3$

然後容斥一下,用類似二位字首和的方式,得到答案$res=calc(x_2,y_2)-calc(x_2,y_1-1)-calc(x_1-1,y_2)+calc(x_1-1,y_1-1)$

檢視程式碼
 void Solve(int TIME) {

    int n, q;cin >> n >> q;
    vvc<int> g(n + 1, vc<int>(n + 1));
    for (int i = 1;i <= n;i++) {
        for (int j = 1;j <= n;j++) {
            char ch;cin >> ch;
            if (ch == 'B') g[i][j] = 1;
        }
    }

    vvc<int> sumG(n + 1, vc<int>(n + 1));
    for (int i = 1;i <= n;i++) {
        for (int j = 1;j <= n;j++) {
            sumG[i][j] = sumG[i][j - 1] + sumG[i - 1][j] - sumG[i - 1][j - 1] + g[i][j];
        }
    }

    auto calc = [&](int x, int y) {
        int res = 0;
        res += (x / n) * (y / n) * sumG[n][n];
        res += (y / n) * sumG[x % n][n];
        res += (x / n) * sumG[n][y % n];
        res += sumG[x % n][y % n];
        return res;
        };

    while (q--) {
        int x1, y1, x2, y2;cin >> x1 >> y1 >> x2 >> y2;
        x1 += 1, y1 += 1, x2 += 1, y2 += 1;
        cout << calc(x2, y2) - calc(x2, y1 - 1) - calc(x1 - 1, y2) + calc(x1 - 1, y1 - 1) << endl;
    }

}

8.青蛙的約會

知識點

exgcd

題解

$x+am=y+an+kL$

$a(m-n)-kL=y-x$

檢視程式碼
 void Solve(int TIME) {

    int x, y, m, n, l;cin >> x >> y >> m >> n >> l;
    int a, k;
    int d = exgcd(m - n, -l, a, k);
    y -= x;
    if (y % d) return cout << "Impossible" << endl, void();
    int base = y / d;
    a *= base, k *= base;
    a = norm(a, l / gcd(l, m - n));
    cout << a << endl;

}

9.C-Prime Distance

知識點

線性篩,埃篩

題解

可以發現只需要用先線性篩預處理出$\sqrt{值域}$範圍內的質數,然後對指定區間埃篩即可。

複雜度$O(nloglog{10^6})$

檢視程式碼
 void Solve(int TIME) {

    int l, r;cin >> l >> r;
    vi v(r - l + 10);
    for (auto i : p) {
        if (i > r) continue;
        for (int j = l / i;j <= r / i;j++) {
            if (j == 1) continue;
            if (i * j >= l) v[j * i - l] = 1;
        }
    }
    if (l == 1) v[0] = 1;
    vi ans;
    for (int i = 0;i <= r - l;i++) {
        if (v[i] == 0) ans.push_back(i + l);
    }
    if (ans.size() < 2) cout << "There are no adjacent primes." << endl;
    else {
        int mn = inf;
        int res = 1;
        int mx = 0;
        int res2 = 1;
        for (int i = 1;i < ans.size();i++) {
            if (ans[i] - ans[i - 1] < mn) {
                mn = ans[i] - ans[i - 1];
                res = i;
            }
            if (ans[i] - ans[i - 1] > mx) {
                mx = ans[i] - ans[i - 1];
                res2 = i;
            }
        }
        cout << ans[res - 1] << "," << ans[res] << " are closest, ";
        cout << ans[res2 - 1] << "," << ans[res2] << " are most distant." << endl;

    }

}

10.X-factor Chains

知識點

多重集排列

題解

按唯一分解定理,將這個數字分解為若干質數的乘積,然後發現相同質因子之間可以交換順序,其實就是個多重集排列。

檢視程式碼
 void Solve(int TIME) {

    int x;cin >> x;
    int res = 0;
    vi ans;
    for (int i = 2;i <= x / i;i++) {
        int cnt = 0;
        while (x % i == 0) {
            res += 1;
            cnt += 1;
            x /= i;
        }
        if (cnt)  ans.push_back(cnt);
    }
    if (x > 1) res += 1, ans.push_back(1);

    vi fact(22);fact[0] = 1;
    for (int i = 1;i <= 20;i++) {
        fact[i] = fact[i - 1] * i;
    }
    debug(fact[20]);
    cout << res << " ";
    res = fact[res];
    for (auto i : ans) {
        res /= fact[i];
    }

    cout << res << endl;

}

11.CF1840G2 - In Search of Truth

知識點

meet in the middle

題解

G1

用類似BSGS的思想,其實也是meet in the middle的思想。或者更類似根號分治?

令$T=\lceil\sqrt{n}\rceil$,$i \in[1,T]$,$j\in[1,T]$ ,這樣$T\cdot i-j$可以表示所有$[0,n-1]$的數。

想要得到答案,可以令$T\cdot i-j\equiv 0(\mod n)$,那麼有$T\cdot i \equiv j (\mod n)$

先預處理出前$T$個數放入雜湊表,然後再每次走$T$步看有沒有和前$T$個數相同的數。

檢視程式碼
 void Solve(int TIME) {

    vi vis(1e6 + 1, -1);
    int now;cin >> now;
    vis[now] = 0;
    auto add = [&](int x) {
        cout << "+ " << x << endl;
        cin >> now;
        };
    auto answ = [&](int x) {
        cout << "! " << x << endl;
        };
    int res = now;
    for (int i = 1;i <= 1000;i++) {
        add(1);
        if (vis[now] != -1) {
            answ(res);
            return;
        }
        res = max(res, now);
        vis[now] = i;
    }

    for (int i = 1;;i++) {
        add(1000);
        if (vis[now] != -1) {
            answ((i + 1) * 1000 - vis[now]);
            return;
        }
    }

}

G2

還是看不懂啊,明年再來看吧

類似題 2022區域賽杭州站I - Guess Cycle Length

12.K-寂寞沙洲冷

知識點

01分數規劃

題解

$k$個生成樹,也就是一開始是$n$棵樹(連通塊),每次連邊都會減少一個連通塊,於是最後變成k個的時候退出就可以了。

然後邊權就是典型的01分數規劃求比率生成樹。注意這裡求最大生成樹。

Dinkelbach方法
 void Solve(int TIME) {

    int n, m, k;cin >> n >> m >> k;
    vc<ai4> edge;
    for (int i = 1;i <= m;i++) {
        int x, y, a, b;cin >> x >> y >> a >> b;
        edge.push_back({ x,y,a,b });
    }
    const ld eps = 1e-8;
    ld ans = eps;
    while (1) {
        DSU d(n + 1);
        vc<pr<ld, ai4>> g;
        for (auto [x, y, a, b] : edge) {
            g.push_back({ -a + b * ans,{x,y,a,b} });
        }
        sort(g.begin(), g.end());
        ld res = 0;int now = n;
        int A = 0, B = 0;
        for (auto [w, u] : g) {
            if (d.find(u[0]) != d.find(u[1])) {
                d.merge(u[0], u[1]);
                A += u[2];
                B += u[3];
                now -= 1;
                if (now == k) break;
            }
        }
        ld t = 1.0 * A / B;
        if (abs(t - ans) < eps) break;
        ans = t;
    }
    cout << ans << endl;

}

二分方法
 void Solve(int TIME) {
 
    int n, m, k;cin >> n >> m >> k;
    vc<ai4> edge;
    for (int i = 1;i <= m;i++) {
        int x, y, a, b;cin >> x >> y >> a >> b;
        edge.push_back({ x,y,a,b });
    }
    const ld eps = 1e-8;
    ld l = eps, r = 1e4 + 10;
    while (r - l > eps) {
        DSU d(n + 1);
        ld mid = (l + r) / 2;
        vc<pr<ld, ai2>> g;
        for (auto [x, y, a, b] : edge) {
            g.push_back({ -a + b * mid,{x,y} });
        }
        sort(g.begin(), g.end());
        ld res = 0;int now = n;
        for (auto [w, u] : g) {
            if (d.find(u[0]) != d.find(u[1])) {
                d.merge(u[0], u[1]);
                res += -w;
                now -= 1;
                if (now == k) break;
            }
        }
        if (res > eps) l = mid;
        else r = mid;
    }
    cout << l << endl;
 
}

二分慢了4~5倍。

13.G-scx的記憶碎片

知識點

dp,組合數

題解

dp求出用1~9組成w位,總和為k的方案數。然後組合數搞一下。

注意這裡m很大,組合數需要特殊處理。

注意特判k=1的情況。

檢視程式碼
 int comb(int a, int b, int mod = MOD) {//C(a,b)=A(a,b)/b!
    if (b < 0 || a < 0 || b > a) return 0;
    int res = 1;
    for (int i = a;i >= a - b + 1;i--) res = i % mod * res % mod;
    res = res * infact[b] % mod;
    return res;
}
void Solve(int TIME) {

    int m, k;cin >> m >> k;
    int W = min(100ll, m);
    vvc<int> f(W + 1, vi(k + 1));
    f[0][0] = 1;
    for (int w = 1;w <= W;w++) {
        for (int i = 0;i <= k;i++) {
            for (int j = 1;j <= 9;j++) {
                if (i >= j) f[w][i] = (f[w][i] + f[w - 1][i - j]) % MOD;
            }
        }
    }

    int res = 0;

    for (int i = 0;i <= W;i++) {
        int cnt = f[i][k];
        res = (res + cnt * comb(m, i) % MOD) % MOD;
    }
    if (k == 1) res = (res + 1) % MOD;
    cout << res << endl;

}

14.5.數學尖子生

知識點

容斥

題解

容易推知,如果一個數$x$被$lcm(1,2,...n-1)$整除,但是不被$lcm(1,2,...n)$整除,那麼這個數的$f(x)=n$。

於是預處理$Lcm$,然後容斥即可。

檢視程式碼
 void Solve(int TIME) {

    int lc = 1;
    vi Lcm(50);
    for (int i = 1;i <= 42;i++) {
        lc = lcm(lc, i);
        Lcm[i] = lc;
    }
    int t;cin >> t;
    while (t--) {
        int a, n;cin >> a >> n;
        if (a >= 42 || a == 1) {
            cout << 0 << endl;
            continue;
        }
        int res = n / Lcm[a - 1] - n / Lcm[a];
        if (res <= 0) cout << 0 << endl;
        else cout << res << endl;
    }

}

15.4.取餘

知識點

題解

如果範圍小一點,可以直接數論分塊$O(a\sqrt {b})$做,但是這裡範圍比較大所以考慮換其他方法。

考慮列舉$b$,這樣每個合法的數的範圍是$[bx+l,bx+r]$,其中$l=s$,$r=min(b-1,t)$,x=0,1,2...

然後根據這個範圍計算一下有幾個數符合條件,需要特判$l=0$和最後一個塊的東西。不得不說調特判的能力是非常重要的!

檢視程式碼
 void Solve(int TIME) {

    int A, B, s, t;cin >> A >> B >> s >> t;
    int res = 0;
    for (int b = 1;b <= B;b++) {
        if (b <= s) continue;
        int l = s, r = min(b - 1, t);
        int x = A / b;
        res += (r - l + 1) * x;
        if (l == 0) res -= 1;
        int t = A - b * x;
        if (t < l) continue;
        res += min(t, r) - l + 1;
    }
    cout << res << endl;

}

16.CF1861E - Non-Intersecting Subpermutations

知識點

容斥,劃分型dp

題解

法1.

顯然有個貪心的策略,應該儘量選擇靠前的合法串。

考慮貢獻的思想,以每個位置為結尾的合法段都求出貢獻然後累加。

設$f_i$表示前$i$個位置中,最後一個長度為$k$的段以第$i$個位置為結尾的貢獻次數。

顯然可以先將$f_i=k!\cdot k^{i-k}$,然後減去不合法的方案。

於是不合法等價於,存在一個$j\in[i-k+1,i-1]$,使得$j$位置為結尾存在一個合法段。

於是方案數就是$f_j \cdot (i-j)!$,$(i-j)!$表示剩下的數字任意次序。

於是有$f_i=k!\cdot k^{i-k}-\sum\limits_{j=i-k+1}^{i-1}f_j \cdot(i-j)!$,後面的$n-i$個數隨便選。

於是答案為$\sum\limits_{i=1}^{n}f_i\cdot k^{n-i}$

檢視程式碼
 void Solve(int TIME) {
 
    int n, k;cin >> n >> k;
    vi f(n + 1);
    int res = 0;
    for (int i = k;i <= n;i++) {
        int t = 0;
        for (int j = i - k + 1;j <= i - 1;j++) {
            t = Add(t, mul(f[j], fact[i - j]));
        }
        f[i] = Dec(mul(fact[k], qp(k, i - k)), t);
        res = Add(res, mul(f[i], qp(k, n - i)));
    }
    cout << res << endl;
 
}

法2.

從前往後掃,如果找到了一個長度為$k$的且滿足條件的段就選擇。

所以可以想到一個$dp$,設$f_{i,j}$表示前$i$個位置,末尾最長的,值只出現一次的子陣列。

$f_{i+1,j+1} += f_{i,j}\cdot(k-j)$,$(i+1不是[i-j+1,i]出現的值)$

$f_{i+1,j} += f_{i,t}$ ,$t\in[j,k-1] $ ,$(i+1是[i-j+1,i]出現的值)$

$f_{i+1,0}=f_{i+1,k}$,插入一個值後所有的值都出現了,讓長度歸$0$重新開始。

於是答案就是$\sum\limits_{i=1}^{n}f_{i,k}\cdot k^{n-i}$

其實也用了貢獻的思想。不過這裡直接dp求出了上面法1容斥求出的東西。

檢視程式碼
 void Solve(int TIME) {

    int n, k;cin >> n >> k;
    vvc<int> f(n + 1, vi(k + 1));
    f[0][0] = 1;
    for (int i = 0;i < n;i++) {
        for (int j = 0;j < k;j++) {
            f[i + 1][j + 1] = Add(f[i + 1][j + 1], mul(f[i][j], (k - j)));
        }
        int s = 0;
        for (int j = k - 1;j >= 1;j--) {
            s = Add(s, f[i][j]);
            f[i + 1][j] = Add(f[i + 1][j], s);
        }
        f[i + 1][0] = f[i + 1][k];
    }
    int res = 0;
    for (int i = 1;i <= n;i++) {
        res = Add(res, mul(f[i][k], qp(k, n - i)));
    }
    cout << res << endl;

}

17.CF1790E - Vlad and a Pair of Numbers

考慮把$a+b$換成位運算子號,才方便構造。

$a\text{^} =a|b-a\&b=x$

$a+b=a|b+a\&b=2x$

得 $a\&b=\frac{x}{2}$ ,又根據$a\text{^} b=x$

可以很容易構造解,分別看$x$和$y=\frac{x}{2}$的每一位數,如果$\{x,y\}=\{0,1\}$則$\{a,b\}=\{1,1\}$;如果$\{x,y\}=\{1,0\}$則$\{a,b\}=\{1,0\}或\{0,1\}$;如果$\{x,y\}=\{0,0\}$則$\{a,b\}=\{0,0\}$;如果$\{x,y\}=\{1,1\}$則無解。根據這個寫一下即可。

檢視程式碼
 void Solve(int TIME) {
 
    int x;cin >> x;
    if (x & 1) return cout << -1 << endl, void();
    int y = x / 2;
    int a = 0, b = 0;
    for (int i = 30;i >= 0;i--) {
        int u = x >> i & 1;
        int v = y >> i & 1;
        if (u == 1 && v == 1) return cout << -1 << endl, void();
        else if (u == 1 && v == 0) a += 1 << i;
        else if (u == 0 && v == 1) a += 1 << i, b += 1 << i;
    }
    cout << a << " " << b << endl;
 
}

期望與機率

1. UVA12004 Bubble Sort

知識點

貢獻計算

題解

考慮每個有序對對答案的貢獻$f(i,j)$,其中$i<j$

$res=\sum\limits_{i=1}^{n}\sum\limits_{j=i+1}^{n}f(i,j)$。而$f(i,j)=1時$,$i$在$j$的後面。 $f(i,j)=0$時,i在j的前面。這兩種情況的機率都是$\frac{1}{2}$

即$f(i,j)=\frac{1}{2}$, 那麼$res=\frac{n(n+1)}{2}\cdot \frac{1}{2}=\frac{n(n+1)}{4}$

檢視程式碼
 void Solve(int TIME) {

    int n;
    cin >> n;
    ll res = 1ll * n * (n - 1) / 2;
    cout << "Case " << TIME << ": ";
    if (res & 1) {
        cout << res << "/" << 2 << endl;
    }
    else {
        cout << res / 2 << endl;
    }

}

2.ABC326E - Revenge of "The Salary of AtCoder Inc."

知識點
期望dp
題解
期望dp考慮逆推。

設狀態$f_i$表示當前的$x$為$i$時,終止時獲得錢的期望數量

顯然有$f_n=0$,因為此時無論抽中什麼都會中終止。

$f_{n-1}=\frac{a_n}{n}$,即抽中$n$有貢獻,否則終止。

$f_{n-2}=\frac{a_{n-1}+f_{n-1}}{n}+\frac{a_{n}}{n}$

$f_{n-3}=\frac{a_{n-2}+f_{n-2}}{n}+\frac{a_{n-1}+f_{n-1}}{n}+\frac{a_n}{n}$

然後發現$f_n=f_{n+1}+\frac{a_{n+1}+f_{n+1}}{n}$,倒著遞推即可。

檢視程式碼
void Solve(int TIME) {

    int n;cin >> n;
    vi a(n + 1);for (int i = 1;i <= n;i++) cin >> a[i];
    int invn = inv(n);
    vi f(n + 1);//目前x=i,至結束獲得錢的期望
    //f[n-1]=a[n]/n+0,f[n-2]=(a[n-1]+f[n-1])/n+a[n]/n,f[n-3]=(a[n-2]+f[n-2])/n+(a[n-1]+f[n-1])n+(a[n])/n
    f[n] = 0;
    for (int i = n - 1;i >= 0;i--) {
        f[i] = (f[i + 1] + (a[i + 1] + f[i + 1]) % MOD * invn % MOD) % MOD;
    }
    cout << f[0] << endl;

}

3.nc68504M - 讓二追三

知識點

貢獻計算期望

題解

考慮計算每個長度為5的連續子段的對答案貢獻的期望,然後答案就是所有的這些單個的累加,即$(n-4)$個的累加,即$(n-4)\cdot (1-p)(1-p)(1-p)\cdot p\cdot p $

檢視程式碼
 void Solve(int TIME) {

    int a, b, n;cin >> a >> b >> n;
    int p = a * inv(b) % MOD;
    if (n <= 4) return cout << 0 << endl, void();
    int res = (n - 4) * (1 - p + MOD) % MOD * (1 - p + MOD) % MOD * p % MOD * p % MOD * p % MOD;
    cout << res << endl;

}

4.牛客挑戰賽71 - A 和的期望

知識點

貢獻計算期望

題解

僅考慮單個數字對答案的貢獻,如果要選出$i$個數字,那麼就需要在選出這個數字後,再在剩下的$n-1$個數字選出$i-1$個。而總方案數是$C_{n}^{i}$,所以答案是$\frac{C_{n-1}^{i-1}S}{C_{n}^{i}}=\frac{(n-1)!}{(i-1)!(n-i)!} \frac{i!(n-i)!}{n!}S=\frac{i}{n}S$,其中$S=\sum\limits_{i=1}^{n}a_i$

檢視程式碼
 void Solve(int TIME) {

    int n;cin >> n;
    vi a(n + 1);for (int i = 1;i <= n;i++) cin >> a[i];

    int s = accumulate(a.begin() + 1, a.end(), 0ll);
    s %= MOD;

    for (int i = 1;i <= n;i++) {
        cout << s * i % MOD * inv(n) % MOD << " ";
    }
    cout << endl;
}

5.abc332F - Random Update Query

知識點

計算期望

題解

發現每次對於一個區間裡的任意一個數,設它的之前的期望是$E$。它的新的期望是$\frac{(r-l)\cdot E}{r-l+1}+\frac{x}{r-l+1}$

維護一個區間加乘線段樹,每次給區間整體乘上$\frac{r-l}{r-l+1}$,然後再加上$\frac{x}{r-l+1}$

檢視程式碼
 struct Segtree {
#define ls (x << 1)
#define rs (x << 1 | 1)
    struct Tag {
        ll k = 1;
        ll b = 0;
    };
    struct Info {
        int sz;
        ll sum = 0;
    };
    struct node {
        Info info;
        Tag tag;
    };
    Info friend operator +(const Info& l, const Info& r) {
        return { l.sz + r.sz,l.sum + r.sum };
    }
    Info friend operator +(const Info& info, const Tag& tag) {
        return { info.sz,(tag.k * info.sum + tag.b * info.sz) % MOD };
    }
    Tag friend operator+(const Tag& tag1, const Tag& tag2) {
        return { tag1.k * tag2.k % MOD,(tag1.b * tag2.k + tag2.b) % MOD };
    }
    int n;
    vector<node> tr;
    Segtree(const vector<int>& a, int n) :n(n) {
        tr.resize(n << 2);
        build(a, 1, 1, n);
    }
    void build(const vector<int>& a, int x, int l, int r) {
        tr[x].info.sz = r - l + 1;
        if (l == r) {
            tr[x].info = { 1,a[l] };
            tr[x].tag = { 1,0 };
            return;
        }
        else {
            int mid = l + r >> 1;
            build(a, ls, l, mid);
            build(a, rs, mid + 1, r);
            pushup(x);
        }
    }
    void pushup(int x) {//從下往上更新
        tr[x].info = tr[ls].info + tr[rs].info;
    }
    void settag(int x, Tag tag) {
        tr[x].info = tr[x].info + tag;
        tr[x].tag = tr[x].tag + tag;
    }
    void pushdown(int x) {//下傳標記
        settag(ls, tr[x].tag);
        settag(rs, tr[x].tag);

        tr[x].tag.k = 1;
        tr[x].tag.b = 0;
    }
    //l,r代表操作區間
    void update(int x, int l, int r, int ql, int qr, Tag tag) {
        if (l == ql && r == qr) {
            settag(x, tag);
            return;
        }
        int mid = l + r >> 1;
        pushdown(x);
        if (qr <= mid) update(ls, l, mid, ql, qr, tag);
        else if (mid < ql) update(rs, mid + 1, r, ql, qr, tag);
        else {
            update(ls, l, mid, ql, mid, tag);
            update(rs, mid + 1, r, mid + 1, qr, tag);
        }
        pushup(x);
    }
    Info query(int x, int l, int r, int ql, int qr) {
        if (l == ql && r == qr) return tr[x].info;
        int mid = l + r >> 1;
        pushdown(x);
        if (qr <= mid) return query(ls, l, mid, ql, qr);
        else if (mid < ql) return query(rs, mid + 1, r, ql, qr);
        else return query(ls, l, mid, ql, mid) + query(rs, mid + 1, r, mid + 1, qr);
    }

};

void Solve(int TIME) {

    int n, m;cin >> n >> m;
    vi a(n + 1);for (int i = 1;i <= n;i++) cin >> a[i];
    Segtree tr(a, n);
    for (int i = 1;i <= m;i++) {
        int l, r, x;cin >> l >> r >> x;
        int len = r - l + 1;
        tr.update(1, 1, n, l, r, { (MOD + 1 - inv(len)) % MOD, inv(len) * x % MOD });
    }

    for (int i = 1;i <= n;i++) {
        cout << tr.query(1, 1, n, i, i).sum << " \n"[i == n];
    }

}

6.CF103637A - Agile permutation

知識點

置換環,斯特林排列數

題解

肯定是先一直洗牌,直到某個大於等於決策點不再洗牌,然後一直使用交換操作。

列舉這個決策點。決策點即需要交換的次數,在這裡即為置換環的個數。

考慮置換環的個數,顯然能用斯特林排列數表示。用$SA_{n,k}$表示$1\sim n$的排列中,環的個數為$k$的方案數。

每次同時計算出大於等於決策點的總值和次數,兩者相除就是交換操作的期望代價。同時由於是幾何分佈,期望等於機率的倒數,計算出隨機操作的期望代價。兩者相加就是該決策點是期望。對所有決策點取最小值即為答案。

還有注意不隨機也要計算一下答案,dfs得到置換環數量之後計入即可。

檢視程式碼
 void Solve(int TIME) {

    int n, a, b;cin >> n >> a >> b;
    vi p(n + 1);for (int i = 1;i <= n;i++) cin >> p[i];
    vc<ld> fact(n + 1);fact[0] = 1.0;
    for (int i = 1;i <= n;i++) {
        fact[i] = fact[i - 1] * i;
    }
    vvc<int> SA(n + 1, vi(n + 1));
    SA[0][0] = 1;
    for (int i = 1;i <= n;i++)
        for (int j = 1;j <= i;j++)
            SA[i][j] = SA[i - 1][j - 1] + (i - 1) * SA[i - 1][j];

    vi vis(n + 1);
    auto dfs = [&](auto&& dfs, int u) {
        if (vis[u]) return;
        vis[u] = 1;
        dfs(dfs, p[u]);
        };
    int tot = 0;
    for (int i = 1;i <= n;i++) {
        if (!vis[i]) dfs(dfs, i), tot++;
    }
    ld res = (n - tot) * a;
    ld cnt = 0, val = 0;
    for (int i = n;i >= 1;i--) {
        cnt += SA[n][i], val += 1.0 * SA[n][i] * a * (n - i);
        res = min(res, fact[n] / cnt * b + val / cnt);
    }
    cout << res << endl;

}

7.機器人

知識點

無窮級數$\frac{1}{1-x}$

題解

發現要停在一個地方,首先需要先到這個地方,然後轉任意圈(可能為0)之後停在這個地方。預處理出轉一圈的機率$P$。

設到達某地的機率為$q$,那麼停在這個地方的機率是$q+qP+qP^2+...=\frac{q}{1-P}$。

排名的話,手玩一下發現排名依次降低(除了0),額外把為0的項放到最後。

檢視程式碼
 void Solve(int TIME) {

    int n;cin >> n;
    vi k(n + 1);for (int i = 1;i <= n;i++) cin >> k[i];
    int inv2 = inv(2);
    vi INV(5e6 + 1);INV[0] = 1;
    for (int i = 1;i <= 5e6;i++) INV[i] = INV[i - 1] * inv2 % MOD;
    vi p(n + 1);for (int i = 1;i <= n;i++) p[i] = INV[k[i]];
    int pi = 1;
    for (int i = 1;i <= n;i++) {
        pi = pi * p[i] % MOD;
    }
    vi f(n + 1);
    int now = 0;
    int nnow = 0;
    for (int i = 1;i <= n;i++) {
        if (k[i]) f[i] = ++now;
        else f[i] = ++nnow;
    }
    for (int i = 1;i <= n;i++) if (k[i] == 0) f[i] = f[i] + now;
    int t = 1;
    int res = 0, res2 = 0;
    for (int i = 1;i <= n;i++) {
        res = (res + i * t % MOD * (MOD + 1 - p[i]) % MOD * inv(MOD + 1 - pi) % MOD) % MOD;
        res2 = (res2 + i * f[i] % MOD) % MOD;
        t = t * p[i] % MOD;
    }
    cout << res << endl;
    cout << res2 << endl;

}

8.P9777 Fujisaki 討厭數學

知識點

矩陣快速冪

題解

令$a_n=x^n+\frac{1}{x^n}$

$(x+\frac{1}{x})a_{n}=x^{n+1}+x^{n-1}+\frac{1}{x^{n-1}}+\frac{1}{x^{n+1}}=a_{n+1}+a_{n-1}$

即$a_n=(x+\frac1x)a_{n-1}-a_{n-2}=ka_{n-1}-a_{n-2}$​

$\left[\begin{array}\ a_1 &a_2 \end{array}\right] \times\left[\begin{array}\ 0&-1\\1&k \end{array}\right]=\left[\begin{array}\ a_2&a_3 \end{array}\right]$

矩陣快速冪加速即可,$a_1=k,a_2=k^2-2$

檢視程式碼
 void Solve(int TIME) {

    int k, n;cin >> MOD >> k >> n;
    if (n == 0) return cout << 2 << endl, void();
    mat<int> b;b(1, 1) = 0;b(1, 2) = MOD - 1, b(2, 1) = 1, b(2, 2) = k;
    b = qp(b, n - 1);
    mat<int> a;a(1, 1) = k, a(1, 2) = Dec(mul(k, k), 2);
    a = a * b;
    cout << a(1, 1) << endl;

}

9.D. Lonely Mountain Dungeons (codeforces.com)

知識點

凸函式

題解

固定了隊伍數量,那麼不同小隊裡同一物種的分配越均勻越好。

假設共$m$只隊伍,$n$個人,設每個隊的人數$x_1,x_2,...,x_m$,即$\frac12(x_1(n-x_1)+x_2\cdot(n-x_2)+...+x_m(n-x_m))=\frac12(n(x_1+...+x_m)-(x_1^2+...+x_m^2))$最大,即$\frac12(n^2-(x_1^2+...+x_m^2))$,顯然平均分配是最好的。

資料範圍很弱,隨便模擬一下即可。

檢視程式碼
 void Solve(int TIME) {
 
    int n, b, x;cin >> n >> b >> x;
    map<int, int> mp;
    vi c(n + 1);for (int i = 1;i <= n;i++) cin >> c[i], mp[c[i]]++;
    sort(c.begin() + 1, c.end());
    int mx = c[n];
    auto calc = [&](int x, int gr) {
        int avg = x / gr;
        int re = x % gr;
        int c1 = gr - re, c2 = re;
        return (x * x - c1 * avg * avg - c2 * (avg + 1) * (avg + 1)) / 2;
        };
    int res = 0, now = 0;
    for (int i = 1;i <= mx;i++) {
        if (mp.count(i)) {
            now += mp[i] * calc(i, i);
        }
        int tnow = now;
        for (auto it = mp.upper_bound(i);it != mp.end();it++) {
            auto [j, k] = *it;
            tnow += k * calc(j, i);
        }
        res = max(res, tnow * b - (i - 1) * x);
    }
    cout << res << endl;
 
 
}

打個表看出這是個凸函式,三分也可以做,不過不太會證明為什麼是凸函式?

檢視程式碼
 void Solve(int TIME) {

    int n, b, x;cin >> n >> b >> x;
    map<int, int> mp;
    vi c(n + 1);for (int i = 1;i <= n;i++) cin >> c[i], mp[c[i]]++;
    sort(c.begin() + 1, c.end());
    int mx = c[n];
    auto calc = [&](int x, int gr) {
        int avg = x / gr;
        int re = x % gr;
        int c1 = gr - re, c2 = re;
        return (x * x - c1 * avg * avg - c2 * (avg + 1) * (avg + 1)) / 2;
        };

    auto check = [&](int k) {
        if (k <= 0) return -inf;
        int res = 0;
        for (auto [i, j] : mp) {
            res += j * calc(i, k);
        }
        return res * b - (k - 1) * x;
        };

    int l = 1, r = mx;
    while (l <= r) {
        int mid = l + r >> 1;
        if (check(mid - 1) < check(mid)) l = mid + 1;
        else r = mid - 1;
    }

    cout << check(l - 1) << endl;

}

10.6.集合劃分【演算法賽】 - 藍橋雲課 (lanqiao.cn)

知識點

容斥

題解

每個相同位上有$1$的,不能分到同一個集合。但是這樣不好算,考慮它的反面,所有該位有$1$的,都分到同一個集合。然後容斥算一下即可,即考慮哪些位需要滿足都分到一個集合。這個用並查集算即可。

檢視程式碼
 void Solve(int TIME) {

    int n;cin >> n;
    DSU d(n + 1);
    vi a(n + 1);for (int i = 1;i <= n;i++) cin >> a[i];
    int u = 0;for (int i = 1;i <= n;i++) u |= a[i];
    auto check = [&](int x) {
        vvc<int> del(15);
        int one = 0;
        for (int i = 0;i < 15;i++) {
            if (x >> i & 1) {
                one += 1;
                for (int j = 1;j <= n;j++) {
                    if (a[j] >> i & 1) {
                        if (del[i].size()) d.merge(del[i].back(), j);
                        del[i].push_back(j);
                    }
                }
            }
        }
        int cnt = 0;
        for (int i = 1;i <= n;i++) {
            cnt += d.find(i) == i;
        }
        for (auto i : del) for (auto j : i) d.p[j] = j;
        if (one & 1) return MOD - qp(2, cnt);
        else return qp(2, cnt);
        };

    for (int i = 0;i < 15;i++) {
        if (u >> i & 1) {
            int cnt = 0;
            for (int j = 1;j <= n;j++) {
                if (a[j] >> i & 1) cnt++;
            }
            if (cnt == 1) {
                return cout << 0 << endl, void();
            }
        }
    }

    int res = 0;
    for (int mask = u;;mask = (mask - 1) & u) {
        res = (res + check(mask)) % MOD;
        if (mask == 0) break;
    }
    cout << res << endl;

}

由於本題資料很弱導致複雜度$2^n$的解也能過。

錯解程式碼
void Solve(int TIME) {

    int n;cin >> n;
    vi a(n + 1);for (int i = 1;i <= n;i++) cin >> a[i];
    map<ai2, int> mp;
    mp[{0, 0}] = 1;
    int mask = 0;
    int u = (1 << 15);
    for (int i = 1;i <= n;i++) {
        mask |= a[i];
        map<ai2, int> nmp;
        for (auto [c, k] : mp) {
            auto [x, y] = c;
            (nmp[{x | a[i], y}] += k) %= MOD;
            (nmp[{x, y | a[i]}] += k) %= MOD;
        }
        swap(nmp, mp);
    }
    cout << mp[{mask, mask}] << endl;

}

11.E - 7x7x7 (atcoder.jp)

知識點

容斥,公共部分經典trick

首先對於兩個在$x$軸上的線段$(x_1,x_2),(x_3,x_4)$來說,公共部分的範圍應該是$max(x_1,x_2)\le min(x_3,x_4)$。推廣到二維的矩形和三維的立方體也是一樣的道理。

由容斥原理,

$v3=V(a\cap b\cap c)$,$v_2=V(a\cap b)+V(b\cap c)+V(a\cap c)-3v_3,v_1=V_{總}-2v_2-3v_3$

暴力列舉第二個和第三個的位置,複雜度$14^6$,看看是否有位置符合。

檢視程式碼
 void Solve(int TIME) {

    int V1, V2, V3;cin >> V1 >> V2 >> V3;
    int V = 3 * 7 * 7 * 7;

    auto func = [&](int x1, int y1, int z1, int x2, int y2, int z2) {
        int res = 1;
        res *= max(0ll, min(x1, x2) + 7 - max(x1, x2));
        res *= max(0ll, min(y1, y2) + 7 - max(y1, y2));
        res *= max(0ll, min(z1, z2) + 7 - max(z1, z2));
        return res;
        };
    auto func2 = [&](int x1, int y1, int z1, int x2, int y2, int z2, int x3, int y3, int z3) {
        int res = 1;
        res *= max(0ll, min({ x1, x2,x3 }) + 7 - max({ x1, x2,x3 }));
        res *= max(0ll, min({ y1, y2,y3 }) + 7 - max({ y1, y2,y3 }));
        res *= max(0ll, min({ z1, z2,z3 }) + 7 - max({ z1, z2,z3 }));
        return res;
        };

    for (int a1 = -7;a1 <= 7;a1++) {
        for (int b1 = -7;b1 <= 7;b1++) {
            for (int c1 = -7;c1 <= 7;c1++) {
                for (int a2 = -7;a2 <= 7;a2++) {
                    for (int b2 = -7;b2 <= 7;b2++) {
                        for (int c2 = -7;c2 <= 7;c2++) {
                            int v3 = func2(0, 0, 0, a1, b1, c1, a2, b2, c2);
                            int v2 = func(a1, b1, c1, a2, b2, c2) + func(0, 0, 0, a1, b1, c1) + func(0, 0, 0, a2, b2, c2) - 3 * v3;
                            int v1 = V - 2 * v2 - 3 * v3;
                            if (v1 == V1 && v2 == V2 && v3 == V3) {
                                YES;
                                cout << 0 << " " << 0 << " " << 0 << " ";
                                cout << a1 << " " << b1 << " " << c1 << " ";
                                cout << a2 << " " << b2 << " " << c2 << endl;
                                return;
                            }
                        }
                    }
                }
            }
        }
    }
    NO;

}

D. Exam in MAC (codeforces.com)

知識點

容斥

題解

對於$a_i$,有$\lfloor\frac{a_i}{2}\rfloor+1$對$(x,y)$,滿足$x+y=a_i$。

有$c-a_i+1$對$(x,y)$滿足$y-x=a_i$。

之後算一下同時滿足兩個條件的對數,分奇偶算一下即可。

然後容斥一下。

檢視程式碼
 void Solve(int TIME) {

    int n, c;cin >> n >> c;
    vi a(n + 1);for (int i = 1;i <= n;i++) cin >> a[i];
    int res = (c + 1) * (c + 2) / 2;
    ai2 par = { 0,0 };
    for (int i = 1;i <= n;i++) {
        res -= a[i] / 2 + 1;
        res -= c - a[i] + 1;
        par[a[i] & 1]++;
    }
    res += par[0] * (par[0] + 1) / 2;
    res += par[1] * (par[1] + 1) / 2;
    cout << res << endl;

}

17屆浙江省賽F

知識點

線性規劃Simplex

題解

原本$x\in[-c,c]$,那麼首先給所有的$x$減去一個$c$,使得$x$可以取$[0,2c]$。

接下來直接套板子即可。

$w_1x_1+...+w_mx_m+b_1>0$,$w_1'x_1+...+w_m'x_m+b_2<0$

加個$eps$給$AX\le B$變成$>0$和$<0$即可。

最後的答案記得減去$c$。

檢視程式碼
 const ld eps = 1e-10;
constexpr int MAXN = 405, MAXM = 405;
struct Simplex {
    inline static ld a[MAXN][MAXM], b[MAXN], c[MAXM];//矩陣a,約束條件b,
    inline static ld d[MAXN][MAXM], x[MAXM];
    inline static int ix[MAXN + MAXM];
    Simplex() {}
    ld run(int n, int m) {
        m++;
        for (int i = 0; i < m + 1; i++) d[n][i] = d[n + 1][i] = 0;
        for (int i = 0; i < n + m; i++) ix[i] = i;

        int r = n, s = m - 1;
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < m - 1; ++j) d[i][j] = -a[i][j];
            d[i][m - 1] = 1;
            d[i][m] = b[i];
            if (d[r][m] > d[i][m]) r = i;
        }
        for (int i = 0;i < m - 1;i++) d[n][i] = c[i];
        d[n + 1][m - 1] = -1;
        for (ld dd; ; ) {
            if (r < n) {
                swap(ix[s], ix[r + m]);
                d[r][s] = 1.0 / d[r][s];
                for (int j = 0;j <= m;j++) if (j != s) d[r][j] *= -d[r][s];
                for (int i = 0;i <= n + 1;i++) {
                    if (i != r) {
                        for (int j = 0;j <= m;j++) {
                            if (j != s) {
                                d[i][j] += d[r][j] * d[i][s];
                            }
                        }
                        d[i][s] *= d[r][s];
                    }
                }
            }
            r = s = -1;
            for (int j = 0;j < m;j++) {
                if (s < 0 || ix[s] > ix[j]) {
                    if (d[n + 1][j] > eps || (d[n + 1][j] > -eps && d[n][j] > eps)) s = j;
                }
            }
            if (s < 0) break;
            for (int i = 0; i < n; ++i) {
                if (d[i][s] < -eps) {
                    if (r < 0 || (dd = d[r][m] / d[r][s] - d[i][m] / d[i][s]) < -eps || (dd < eps && ix[r + m] > ix[i + m])) r = i;
                }
            }
            if (r < 0) return -1;//無解
        }
        if (d[n + 1][m] < -eps) return -1;
        ld ans = 0;
        for (int i = 0;i < m;i++) x[i] = 0;
        for (int i = m; i < n + m; i++) {
            if (ix[i] < m - 1) {
                ans += d[i - m][m] * c[ix[i]];
                x[ix[i]] = d[i - m][m];
            }
        }
        return ans;
    }
};

const int C = 9000;
const ld eps2 = 1e-7;

void Solve(int TIME) {

    int n;cin >> n;
    vvc<int> A(2, vi(n));
    vi B(2);
    for (int i = 0;i < 2;i++) {
        int sum = 0;
        for (int j = 0;j < n;j++) {
            cin >> A[i][j];sum += A[i][j];
        }
        cin >> B[i];
        B[i] -= C * sum;
    }
    Simplex sim;
    for (int cas = 0;cas < 2;cas++) {
        for (int i = 0;i < n;i++) sim.c[i] = 1;
        for (int i = 0;i < n;i++) for (int j = 0;j < n;j++) sim.a[i][j] = 0;
        for (int j = 0;j < n;j++) {
            sim.a[j][j] = 1;sim.b[j] = 2 * C;
        }
        for (int i = 0;i < 2;i++) {
            for (int j = 0;j < n;j++) sim.a[n + i][j] = 0;
            for (int j = 0;j < n;j++) {
                if (cas == i) sim.a[n + i][j] = -A[i][j];
                else sim.a[n + i][j] = A[i][j];
            }
            if (cas == i) sim.b[n + i] = B[i] - eps2;
            else sim.b[n + i] = -B[i] - eps2;
        }
        ld res = sim.run(n + 2, n);
        if (res == -1) continue;
        for (int j = 0;j < n;j++) cout << sim.x[j] - C << " ";cout << endl;
        return;
    }
    cout << "No" << endl;

}

6.最後的加k【演算法賽】 - 藍橋雲課 (lanqiao.cn)

知識點

矩陣光速冪

題解

注意到每一位是獨立的,可以分別計算。列出$dp$方程,然後用矩陣光速冪預處理同底數同模數的矩陣,之後左乘初始矩陣得到答案。

(初始矩陣可以看作向量,這樣常數會更快一點)

檢視程式碼
 template<typename T>
struct mat {
    int m[11][11];
    mat() {
        memset(m, 0, sizeof m);
    }
    mat(int epsilon) {
        memset(m, 0, sizeof m);
        for (int i = 1;i <= 10;i++) m[i][i] = 1;
    }
    T& operator()(int i, int j) { return m[i][j]; }
    T operator()(int i, int j)const { return m[i][j]; }
    int is_I() {
        for (int i = 1;i <= 10;i++) {
            for (int j = 1;j <= 10;j++) {
                if (i == j && m[i][j] != 1) return 0;
                if (i != j && m[i][j] != 0) return 0;
            }
        }
        return 1;
    }
};



//矩陣乘法
template<typename T>
mat<T> operator *(const mat<T>& x, const mat<T>& y) {
    mat<T> t;
    for (int k = 1;k <= 10;k++) {
        for (int i = 1;i <= 10;i++) {
            for (int j = 1;j <= 10;j++) {
                (t.m[i][j] += x.m[i][k] * y.m[k][j] % MOD) %= MOD;
            }
        }
    }
    return t;
}
//向量乘矩陣
template<typename T>
mat<T> operator &(const mat<T>& x, const mat<T>& y) {
    mat<T> t;
    for (int k = 1;k <= 10;k++) {
        for (int i = 1;i <= 1;i++) {
            for (int j = 1;j <= 10;j++) {
                (t.m[i][j] += x.m[i][k] * y.m[k][j] % MOD) %= MOD;
            }
        }
    }
    return t;
}
//矩陣快速冪
mat<int> qp(mat<int> a, int k) {
    mat<int> res(1);
    while (k) {
        if (k & 1) res = res * a;
        a = a * a;
        k >>= 1;
    }
    return res;
}


//光速冪,處理同底數同模數的冪
namespace LP_Mat {
    ll getphi(ll x) {
        ll res = x;
        for (int i = 2; i * i <= x; i++) {
            if (x % i == 0) {
                res -= res / i;
                while (x % i == 0) x /= i;
            }
        }
        if (x > 1) res -= res / x;
        return res;
    }
    mat<int> base1[N], basesqrt[N];
    int Block_len;
    int Phi;
    ll maxn = 1e9 + 7;//模數的最大值
    void init(mat<int> x) {//初始化底數為x
        Phi = getphi(MOD);
        Block_len = sqrt(maxn) + 1;
        base1[0] = mat<int>(1);for (int i = 1;i <= Block_len;i++) base1[i] = base1[i - 1] * x;
        basesqrt[0] = mat<int>(1);for (int i = 1;i <= Block_len;i++) basesqrt[i] = basesqrt[i - 1] * base1[Block_len];
    }
    mat<int> qp(ull x) {
        x %= Phi;
        return basesqrt[x / Block_len] * base1[x % Block_len];
    }
}



void Solve(int TIME) {

    int T, x;cin >> T >> x;
    mat<int> base;
    for (int i = 0;i <= 9;i++) {
        int nx = x + i;
        auto s = to_string(nx);
        for (auto j : s) base(i + 1, j - '0' + 1) += 1;
    }
    LP_Mat::init(base);
    while (T--) {
        int n, k;cin >> n >> k;
        mat<int> m;
        for (auto i : to_string(n)) m(1, i - '0' + 1) += 1;
        m = m & LP_Mat::qp(k);
        int res = 0;
        for (int i = 1;i <= 10;i++) res = (res + m(1, i)) % MOD;
        cout << res << endl;
    }

}

P3746 [六省聯考 2017] 組合數問題

知識點

矩陣快速冪

題解

$\sum\limits_{i=0}^{n}\left(\begin{array}\ nk\\ik+r \end{array}\right)=\sum\limits_{i=0}^{in+r}\left(\begin{array}\ nk\\i \end{array}\right)[i\equiv r(mod \; k)]$

即從$nk$個物品中選$i$個物品,且$i\equiv r(mod\;k)$的方案數。

於是考慮dp,$f_{i,j}$表示前$i$個物品,選擇$j$個(注意需要在模$k$意義下)的方案數。

那麼有$f_{i,j}=f_{i-1,j}+f_{i-1,(j-1)\;mod\; k}$,矩陣快速冪即可。注意特判k=1。

檢視程式碼
void Solve(int TIME) {

    mat<int> ma;
    int n, k, r;cin >> n >> MOD >> k >> r;
    if (k == 1) return cout << qp(2, n) << endl, void();
    n *= k;
    for (int i = 0;i < k;i++) {
        ma(i, i) = ma((i - 1 + k) % k, i) = 1;
        //ma(i, i) = ma(i, (i - 1 + k) % k) = 1;原本寫反成這個也能透過!?思考一下資料水了還是為啥?
    }

    mat<int> res;
    res(0, 0) = 1;
    res = res * qp(ma, n);
    cout << res(0, r) << endl;

}

6.組合數【演算法賽】 - 藍橋雲課 (lanqiao.cn)

雙倍經驗

檢視程式碼
 void Solve(int TIME) {

    mat<int> ma;
    int a, b, n;cin >> a >> b >> n;

    if (a == 1) return cout << qp(2, n) << endl, void();

    for (int i = 0;i < a;i++) {
        ma(i, i) = ma((i - 1 + a) % a, i) = 1;
    }
    mat<int> res;
    res(0, 0) = 1;
    res = res * qp(ma, n);
    int ans = res(0, b);
    cout << ans << endl;

}

相關文章