Funcția Möbius

Noțiuni introductive

În teoria numerelor, o funcție aritmetică este o funcție f(n):NCf(n) : \mathbb{N} \to \mathbb{C}. O funcție aritmetică exprimă proprietăți aritmetice pentru nn.

Pentru m,nm, n numere prime între ele (adică cmmdc(m,n)=1\operatorname{cmmdc}(m, n) = 1), avem două feluri de funcții aritmetice:

  • funcții aditive, unde f(mn)=f(n)+f(m)f(mn) = f(n) + f(m);
  • funcții multiplicative, unde f(mn)f(mn) = f(m)f(n)f(m)f(n).

Pentru simplitate vom defini următoarele aspecte:

  • [p]=1[p] = 1 dacă pp este o propoziție adevărată sau 0 în caz contrar.
  • n\lfloor n \rfloor = partea întreagă a lui nn.

Cât și următoarele proprietăți celebre:

  • k=1N1klogN\sum_{k = 1}^{N} \frac{1}{k} \approx \log{N}.
  • Șirul ai=Nia_i = \lfloor \frac{N}{i} \rfloor, cu N\leq N, are O(N)\mathcal{O}(\sqrt N) valori distincte.

Pentru pN\forall p \in \mathbb{N}, pp număr prim, și kN\forall k \in \mathbb{N}, definim următoarele funcții multiplicative:

  • funcția identică I(pk)=pkI(p^k) = p^k;
  • funcția putere Pa(pk)=pkaP_a(p^k) = p^{ka}, unde aa este constantă (nu confundăm cu funcția exponențială fa(pk)=apkf_a(p^k) = a^{p^k});
  • funcția unitate U(pk)=[pk=1]U(p^k) = [p^k = 1];
  • funcția divizorilor σ(pk)\sigma (p^k) = numărul de divizori ai lui pkp^k;
  • indicatorul lui Euler φ(pk)=pkpk1\varphi(p^k) = p^{k} - p^{k-1}, câte numere xx, cu 1xpk1 \leq x \leq p^k și cmmdc(x,pk)=1\operatorname{cmmdc}(x, p^k) = 1 există
  • funcția Möbius μ(pk)=[k=0][k=1]\mu(p^k) = [k = 0] - [k = 1].

Definiție

Două funcții multiplicative, f(n)f(n) și g(n)g(n), sunt identice dacă pentru oricare pp număr prim și oricare k0k \geq 0, g(pk)=f(pk)g(p^k) = f(p^k).

Precalcularea funcțiilor multiplicative

În contextul nostru, vom lucra cel mai des cu funcții multiplicative, iar de cele mai multe ori avem nevoie să știm valorile unei funcții pentru un set mai larg de elemente. Și se dovedește că Ciurul învățat în clasa a 6-a este bun nu numai la aflarea numerelor prime.

Ciurul lui Eratostene

Acest algoritm este poate cel mai popular printre elevii de liceu și gimnaziu pentru a afla numerele prime într-un interval.

vector<int> ciur(N + 1);
ciur[0] = ciur[1] = 1;
for (int i = 2; i <= N; i++) {
    if (ciur[i] == 0) {  // numarul i este prim
        for (int j = 2 * i; j <= N; j += i) {
            ciur[j] = 1;  // j se scrie ca i * p
        }
    }
}

La finalul programului, kk va fi număr prim doar dacă ciur(k)=0\operatorname{ciur}(k) = 0.

Complexitatea de timp este O(k=1NNk)=O(NlogN)\mathcal{O}(\sum_{k=1}^N \frac{N}{k}) = \mathcal{O}(N \log{N}).

Ciur liniar

Observăm că fiecare număr compus XX este parcurs de către cel de-al doilea for de mai multe ori. Dacă am putea să iterăm prin fiecare număr compus exact o singură dată am ajunge la complexitatea de O(N)\mathcal{O}(N). Reținem într-un vector auxiliar numerele prime, și pentru un ii fixat vom parcurge numerele prime până când un număr prim divide ii.

vector<int> prime;
vector<int> is_composite(N + 1);

for (int i = 2; i <= n; i++) {
    if (!is_composite[i]) {
        prime.push_back(i);
    }
    for (int j = 0; j < prime.size() && i * prime[j] <= n; j++) {
        is_composite[i * prime[j]] = 1;
        if (i % prime[j]) {
            break;
        }
    }
}

Demonstrație

Ca să demonstrăm faptul că ciurul de mai sus iterează prin fiecare număr compus exact odată avem nevoie de cel mai mic factor prim al acestuia, pp. Să presupunem că q=ipq = i \cdot p. Pentru oricare j>ij > i, jj este divizor a lui qq, presupunem ca k=qjk = \frac{q}{j} este prim. Cum i<ji < j, atunci k<pk < p, însă pp este cel mai mic număr prim care divide qq, deci nu există un astfel kk. Deci odată luată în considerare perechea (i,p)(i, p),, ipi \cdot p va fi calculat doar o singură dată, transformând complexitatea finală în O(N)\mathcal{O}(N).

Precalcularea indicatorului lui Euler folosind Ciurul Liniar

Pentru a calcula φ(n)\varphi(n) trebuie să luam în considerare 3 cazuri:

  • nn este prim, deci φ(n)=n1\varphi(n) = n-1
  • n=ipn = i \cdot p și pip \nmid i, deci φ(n)=φ(i)φ(p)\varphi(n) = \varphi(i) \varphi(p). (prin aba \nmid b înțelegem: "a nu divide pe b").
  • n=ipn = i \cdot p și pip \mid i. Acest caz este uneori greu de tratat, dar din fericire știm sigur că φ(ip)=pφ(i) i,p\varphi(ip) = p\varphi(i)\ \forall i, p.
vector<int> prime;
vector<int> phi(N), compus(N);
phi[1] = 1;
for (int i = 2; i <= N; i++) {
    if (!compus[i]) {
        prime.push_back(i);
        phi[i] = i - 1;
    }
    for (int j = 0; j < prime.size() && i * prime[j] <= N; j++) {
        compus[i * prime[j]] = 1;
        if (i % prime[j]) {
            phi[i * prime[j]] = phi[i] * phi[prime[j]];
        } else {
            phi[i * prime[j]] = prime[j] * phi[i];
        }
    }
}

Generalizare a ciurului liniar

Totuși, putem să generalizăm algoritmul prezentat mai sus pentru a funcționa pentru oricare funcție multiplicativă. Fie sml(n)\operatorname{sml}(n) puterea celui mai mic factor din descompunerea în factori primi a lui nn. Pentru oricare ii și pp, pp cel mai mic număr prim care divide ii, putem scrie f(ip)=f(ipsml(i))f(psml(i)+1)f(ip) = f(\frac{i}{p^{\operatorname{sml}(i)}}) \cdot f(p^{\operatorname{sml}(i) + 1}).

vector<int> prime, phi(N + 1), compus(N + 1), sml(N + 1);
phi[1] = 1;
for (int i = 2; i <= N; i++) {
    if (!compus[i]) {
        prime.push_back(i);
        phi[i] = i - 1;
        sml[i] = 1;
    }
    for (int j = 0; j < prime.size() && i * prime[j] <= N; j++) {
        compus[i * prime[j]] = 1;
        if (i % prime[j]) {
            phi[i * prime[j]] = phi[i] * phi[prime[j]];
            sml[i * prime[j]] = 1;
        } else {
            phi[i * prime[j]] =
                (phi[i] / (pow(prime[j], sml[i]) - pow(prime[j], sml[i] - 1)));
            phi[i * prime[j]] *=
                (pow(prime[j], sml[i] + 1) - pow(prime[j], sml[i]));
            sml[i * prime[j]] = sml[i] + 1;
        }
    }
}

Atenție

Funcția pow din cod este o funcție scrisă de mână. Nu recomandăm folosirea funcției pow din <cmath>, din cauza erorilor de precizie.

Gândim similar pentru funcția Möbius:

  • nn prim μ(n)=1\Rightarrow \mu(n) = -1
  • n=ipn = i \cdot p, pip \nmid i, deci μ(n)=μ(i)μ(p)\mu(n) = \mu(i) \cdot \mu(p)
  • n=ipn = i \cdot p, pip \mid i, deci μ(n)=μ(i)[sml(i)=0][sml(i)=1]([sml(i)+1=0][sml(i)+1=1])\mu(n) = \frac{\mu(i)}{[sml(i)=0]-[sml(i)=1]} \cdot ([sml(i)+1=0]-[sml(i)+1=1]).

Observație

În cazul în care fracția de mai sus nu este definită (numitorul este 0), putem spune din start că μ(n)=0\mu(n) = 0.

vector<int> prime;
vector<int> sml(N), mobius(N), composite(N);

mobius[1] = 1;
for (int i = 2; i < N; i++) {
    if (!composite[i]) {
        prime.push_back(i);
        mobius[i] = -1;
        sml[i] = 1;
    }
    for (int j = 0; j < prime.size() && i * prime[j] < N; j++) {
        composite[i * prime[j]] = 1;
        if (i % prime[j]) {
            mobius[i * prime[j]] = mobius[i] * mobius[prime[j]];
            sml[i * prime[j]] = 1;
        } else {
            int cltr = (sml[i] == 0) - (sml[i] == 1);
            int pl = (sml[i] + 1 == 0) - (sml[i] + 1 == 1);
            if (cltr == 0) {
                mobius[i] = 0;
            } else {
                mobius[i * prime[j]] = (mobius[i] / cltr) * pl;
            }
            sml[i * prime[j]] = sml[i] + 1;
        }
    }
}

Implementare mai populară

Rareori avem nevoie de ciur liniar, și dacă nu intră în timp O(NlogN)\mathcal{O}(N \log{N}) pentru precalculare, de ce ar intra O(N)\mathcal{O}(N)?

vector<int> phi(N), mobius(N);
phi[1] = mobius[1] = 1;

for (int i = 2; i < N; i++) {
    phi[i] = i - 1;
}
for (int i = 1; i < N; i++) {
    for (int j = 2 * i; j < N; j += i) {
        mobius[j] -= mobius[i];
        if (i > 1) {
            phi[j] -= phi[i];
        }
    }
}

Inversiunea lui Möbius

Ultimele din cele 3 funcții prezentate la începutul articolului sunt mai cunoscute ca restul, însă noi ne vom folosi cel mai mult de ultimele 2, anume indicatorul lui Euler și funcția Möbius.

Fie g(n)g(n) = dnf(d)\sum_{d\mid n} f(d). Inversiunea lui Möbius ne spune:

f(n)=_dng(d)μ(nd)f(n) = \sum\_{d\mid n} g(d) \cdot \mu \left(\frac{n}{d}\right)

Cu toate astea, o proprietate mai importantă este dnμ(d)=U(n)\sum_{d\mid n} \mu(d) = U(n). Ceea ce sugerează expresia este că pentru oricare număr natural nn suma va da 1 doar dacă n=1n = 1. Pare nesemnificativă proprietatea, însă este foarte utilă în rezolvarea multor probleme de informatică.

Exercițiu 1: Calculează câte perechi (a,b)(a,b) (1a,bn1 \leq a,b \leq n) există cu proprietatea că cmmdc(a,b)=1\operatorname{cmmdc}(a,b) = 1.

Rezolvare: Noi trebuie să calculăm i=1nj=1n[cmmdc(i,j)=1]\sum_{i=1}^{n} \sum_{j=1}^{n} [\operatorname{cmmdc}(i, j) = 1]. Ne putem folosi de proprietatea de mai sus și să scriem relația astfel:

i=1nj=1ndcmmdc(i,j)μ(d)\sum_{i=1}^{n} \sum_{j=1}^{n} \sum_{d \mid \operatorname{cmmdc}(i,j)} \mu(d)

Iterăm prin toate numerele n\leq n în loc de divizorii lui nn și obținem

i=1nj=1nd=1nμ(d)[dcmmdc(i,j)]=i=1nj=1nd=1nμ(d)[di][dj]\sum_{i=1}^{n} \sum_{j=1}^{n} \sum_{d = 1}^{n} \mu(d) \cdot [d\mid \operatorname{cmmdc}(i,j)] = \sum_{i=1}^{n} \sum_{j=1}^{n} \sum_{d = 1}^{n} \mu(d) \cdot [d\mid i] \cdot [d\mid j]

Rearanjăm termenii și obținem

d=1nμ(d)(i=1n[di])(j=1n[dj])\sum_{d=1}^{n} \mu(d) \left(\sum_{i=1}^{n} [d\mid i]\right) \left(\sum_{j=1}^{n} [d\mid j]\right)

Observăm că

i=1n[di]=j=1n[dj]=nd\sum_{i=1}^{n} [d\mid i] = \sum_{j=1}^{n} [d\mid j] = \left\lfloor \frac{n}{d} \right\rfloor

deci relația finală devine d=1nμ(d)(nd)2\sum_{d=1}^{n} \mu(d) \cdot (\frac{n}{d})^2, care poate fi calculată în O(n)\mathcal{O}(n).

Exercițiu 2: Calculează câte perechi (a,b)(a,b) exisă, astfel încât 1a,bn1 \leq a,b \leq n și cmmdc(a,b)\operatorname{cmmdc}(a, b) = PP.

Rezolvare:

i=1nj=1n[cmmdc(i,j)=P]=i=1nPj=1nP[cmmdc(i,j)=1] \sum_{i=1}^{n} \sum_{j=1}^{n} [\operatorname{cmmdc}(i,j) = P] = \sum_{i=1}^{\frac{n}{P}} \sum_{j=1}^{\frac{n}{P}} [\operatorname{cmmdc}(i,j) = 1]

Observăm că e identic cu exercițiul precedent, rezultatul fiind d=1nPμ(d)(ndP)2\sum_{d=1}^{\frac{n}{P}} \mu(d) \cdot \left(\frac{n}{dP}\right)^2.\

Exercițiul 3: Calculează 1i,jNlcm(i,j)\sum_{1 \leq i,j \leq N} lcm(i,j), unde lcm(i,j)=lcm(i,j) = cel mai mic multiplu comun al numerelor ii și jj.

Rezolvare: Știm totuși că lcm(i,j)=ijcmmdc(i,j)lcm(i,j) = \dfrac{i\cdot j}{\operatorname{cmmdc}(i,j)}, astfel problema ne cere să calculăm suma:

1i,jNijcmmdc(i,j) \sum_{1 \leq i, j \leq N} \dfrac{i \cdot j}{\operatorname{cmmdc}(i,j)}

Pentru a ne ușura calculul, putem defini:

f(k)=1i,jNijcmmdc(i,j)[cmmdc(i,j)=k] f(k) = \sum_{1 \leq i, j \leq N} \dfrac{i \cdot j}{\operatorname{cmmdc}(i,j)} \cdot [\operatorname{cmmdc}(i,j) = k]

Observăm deci că dacă știm suma produselor iji \cdot j, cu cmmdc(i,j)=k\operatorname{cmmdc}(i,j) = k, fie această sumă p(k)p(k), atunci rezultatul devine:

f(k)=p(k)k f(k) = \dfrac{p(k)}{k}

Pentru a calcula p(k)p(k) ne putem folosi de funcția mobius astfel:

p(k)=1i,jNij[cmmdc(i,j)=k]=a=1Nkb=1Nkabk2[cmmdc(a,b)=1]=a=1Nkb=1Nkabk2d=1Nkμ(d)[da][db]=k2d=1Nkμ(d)(a=1Nka[da])(b=1Nkb[db])\begin{align*} p(k) &= \sum_{1 \leq i,j \leq N} i \cdot j \cdot [\operatorname{cmmdc}(i,j) = k] \\ &= \sum_{a = 1}^{\frac{N}{k}} \sum_{b = 1}^{\frac{N}{k}} a \cdot b \cdot k^2 \cdot [\operatorname{cmmdc}(a,b) = 1] \\ &= \sum_{a = 1}^{\frac{N}{k}} \sum_{b = 1}^{\frac{N}{k}} a \cdot b \cdot k^2 \cdot \sum_{d = 1}^{\frac{N}{k}} \mu(d) \cdot [d \mid a] \cdot [d \mid b] \\ &= k^2 \cdot \sum_{d=1}^{\frac{N}{k}} \mu(d) \cdot \left(\sum_{a = 1}^{\frac{N}{k}} a \cdot [d \mid a] \right) \cdot \left(\sum_{b=1}^{\frac{N}{k}} b \cdot [d \mid b] \right) \end{align*}

Observăm că:

a=1Nka[da]=b=1Nkb[db]=(d(1+2++Nkd))2=(dNkd(Nkd+1)2)2\begin{align*} \sum_{a=1}^{\frac{N}{k}} a \cdot [d \mid a] &= \sum_{b=1}^{\frac{N}{k}} b \cdot [d \mid b] \\ &= \left(d \cdot (1 + 2 + \dots + \frac{N}{kd}) \right)^2 \\ &= \left( d \cdot \frac{\frac{N}{kd} \cdot \left(\frac{N}{kd} + 1\right)}{2} \right)^2 \end{align*}

Deci:

p(k)=k2d=1Nkμ(d)(dNkd(Nkd+1)2)2p(k) = k^2 \cdot \sum_{d = 1}^{\frac{N}{k}} \mu(d) \cdot \left( d \cdot \dfrac{\frac{N}{kd} \cdot (\frac{N}{kd} + 1)}{2} \right)^2

Revenim la problema noastră inițială: f(k)=p(k)k=k_d=1Nkμ(d)(dNkd(Nkd+1)2)2f(k) = \frac{p(k)}{k} = k \cdot \sum\_{d = 1}^{\frac{N}{k}} \mu(d) \cdot \left( d \cdot \dfrac{\frac{N}{kd} \cdot (\frac{N}{kd} + 1)}{2} \right)^2

Iar răspunsul final este k=1Nf(k)\sum_{k=1}^{N} f(k), care este calculabil în O(NlogN)\mathcal{O}(N \log N).

Probleme propuse spre rezolvare

Problema sumgcd

Kilonova

View Problem

Kilonova

Could not fetch problem details

Deschide

Pentru NN și MM date la tastatură, trebuie să calculați Vcmmdc(V)\sum_{V} \operatorname{cmmdc}(V), unde VV reprezintă un MM-tuplu. Un MM-tuplu reprezintă o mulțime de MM elemente nu neapărat distincte cu valori cuprinse între 1 și NN. Formal, noi trebuie să calculam i1=1Ni2=1NiM=1Ncmmdc(i1,i2,,iM)\sum_{i_1 = 1}^{N} \sum_{i_2 = 1}^{N} \dots \sum_{i_M = 1}^{N} \operatorname{cmmdc}(i_1, i_2, \dots, i_M).

Dacă pentru un KK fixat aflăm câte M-tupluri există cu cmmdc-ul egal cu KK, atunci putem rezolva foarte ușor problema. Fie f(K)f(K) numărul de tupluri (m,n)(m, n) pentru care cmmdc(m,n)=K\operatorname{cmmdc}(m, n) = K:

f(K)=i1=1Ni2=1NiM=1N[cmmdc(i1,i2,,iM)=K]=i1=1NKi2=1NKiM=1NK[cmmdc(i1,i2,,iM)=1]=i1=1NKi2=1NKiM=1NKd=1NKμ(d)[di1][diM]=d=1NKμ(d)(i1=1NK[di1])(iM=1NK[diM])=d=1NKμ(d)(NKd)M.\begin{align*} f(K) &= \sum_{i_1 = 1}^{N} \sum_{i_2 = 1}^{N} \dots \sum_{i_M = 1}^{N} [\text{cmmdc}(i_1, i_2, \dots, i_M) = K] \\ &= \sum_{i_1 = 1}^{\frac{N}{K}} \sum_{i_2 = 1}^{\frac{N}{K}} \dots \sum_{i_M = 1}^{\frac{N}{K}} [\text{cmmdc}(i_1, i_2, \dots, i_M) = 1] \\ &= \sum_{i_1 = 1}^{\frac{N}{K}} \sum_{i_2 = 1}^{\frac{N}{K}} \dots \sum_{i_M = 1}^{\frac{N}{K}} \sum_{d = 1}^{\frac{N}{K}} \mu(d) \cdot [d \mid i_1] \cdots [d \mid i_M] \\ &= \sum_{d = 1}^{\frac{N}{K}} \mu(d) \cdot \left(\sum_{i_1 = 1}^{\frac{N}{K}} [d \mid i_1]\right) \cdots \left(\sum_{i_M = 1}^{\frac{N}{K}} [d \mid i_M]\right) \\ &= \sum_{d = 1}^{\frac{N}{K}} \mu(d) \cdot \left(\frac{N}{Kd}\right)^M. \end{align*}

Rezultatul problemei este dat de i=1Nf(i)i\sum_{i=1}^{N} f(i) \cdot i. Complexitatea de timp pentru a calcula f(K)f(K) este O(NKlogM)\mathcal{O}(\frac{N}{K}\log{M}), astfel complexitatea finală este

i=1NO(NilogM)=O(N+N2+N3++NN)logM=O(N(1+12+13++1N)logM)=O(NlogNlogM).\begin{align*} \sum_{i=1}^{N} O\left(\frac{N}{i} \log{M}\right) &= O\left(N + \frac{N}{2} + \frac{N}{3} + \cdots + \frac{N}{N}\right) \log{M} \\ &= O\left(N \left(1 + \frac{1}{2} + \frac{1}{3} + \cdots + \frac{1}{N}\right) \log{M}\right) \\ &= O\left(N \log{N} \log{M}\right). \end{align*}

Altă soluție este următoarea:

Vom pune pe cele MM poziții doar multiplii de KK, astfel se formează NKM\left\lfloor\frac{N}{K} \right\rfloor^M șiruri posibile, dintre care scădem f(KQ),Q1f(K \cdot Q), Q \geq 1.

f(K)=MNKKif(i)=MNKi=1Nf(i)[Ki]=MNKi=1NKf(Ki).\begin{align*} f(K) &= M^{\left\lfloor \frac{N}{K} \right\rfloor} - \sum_{K \mid i} f(i) \\ &= M^{\left\lfloor \frac{N}{K} \right\rfloor} - \sum_{i=1}^{N} f(i) \cdot [K \mid i] \\ &= M^{\left\lfloor \frac{N}{K} \right\rfloor} - \sum_{i=1}^{\frac{N}{K}} f(K \cdot i). \end{align*}

Complexitatea devine:

i=1NO(Ni+logM)=O(N(1+12+13+1N)+NlogM)=O(NlogN+NlogM)=O(N(logN+logM))=O(Nlog(MN))\begin{align*} \sum_{i=1}^{N} \mathcal{O}(\left\lfloor \frac{N}{i} \right\rfloor + \log{M}) &= \mathcal{O}(N \left(1 + \frac{1}{2} + \frac{1}{3} + \dots \frac{1}{N}\right) + N \log{M}) \\ &= \mathcal{O}(N \log{N} + N \log{M}) \\ &= \mathcal{O}(N\left(\log{N} + \log{M}\right)) \\ &= \mathcal{O}(N\log{(MN)}) \end{align*}

Putem precalcula puterile lui MM, obținem astfel O(NlogN)\mathcal{O}(N \log{N}). Ambele iau 100 puncte.

Problema cntgcd

Kilonova

View Problem

Kilonova

Could not fetch problem details

Deschide

Se dau două numere naturale NN și DD. Calculați câte perechi de numere AA și BB mai mici ca NN există, astfel încât cmmdc(A,B)=D\operatorname{cmmdc}(A,B) = D. Perechea (A,B)(A,B) este la fel ca perechea (B,A)(B, A).

Putem să luăm rezultatul de la primul exercițiu, pentru că probleme sunt echivalente. Singura restricție este faptul că perechea (A,B)=(B,A)(A,B) = (B,A), dar putem efectiv să împărțim rezultatul la 2.

ans=d=1NDμ(d)(NdD)2+12ans = \frac{\sum_{d=1}^{\frac{N}{D}} \mu(d) \cdot \left(\frac{N}{dD}\right)^2 + 1}{2}

Soluția ia undeva la 45 puncte, datorită faptului că DN109D \leq N \leq 10^9.

Fie f(n)f(n) = numărul de perechi (A,B)(A,B), unde cmmdc(A,B)=1\operatorname{cmmdc}(A,B) = 1. Noi trebuie să calculăm practic f(ND)=d=1NDφ(d)f(\left\lfloor \frac{N}{D} \right\rfloor ) = \sum_{d = 1}^{\left\lfloor \frac{N}{D} \right\rfloor } \varphi(d).

Pentru N106N \leq 10^6 putem calcula suma brut. Pentru N>106N > 10^6 putem elimina perechile care au cmmdc-ul 2, 3 etc.

f(n)=n2n2d=2nf(nd)f(n) = \frac{n^2 - n}{2} - \sum_{d=2}^{n} f\left(\left\lfloor \frac{n}{d} \right\rfloor\right)

Datorită faptului că șirul ai=Nia_i = \lfloor \frac{N}{i} \rfloor are O(N)\mathcal{O}(\sqrt{N}) elemente diferite, putem doar să calculăm câte numere d1d_1 există, astfel încât nd=nd1\frac{n}{d} = \frac{n}{d_1} și să adunăm la rezultat f(nd)nrf(\lfloor \frac{n}{d} \rfloor) \cdot nr.

Observație

Fie dd cel mai mic număr astfel încât nd=x\frac{n}{d} = x. Atunci cel mai mare număr care îndeplinește aceeași proprietate este nnd\left\lfloor \frac{n}{\lfloor \frac{n}{d} \rfloor} \right\rfloor.

long long f(long long n) {
    // cout << n << '\n';
    if (n <= 1000000) {
        return sum_phi[n];  // phi(1) + phi(2) + ... + phi(n)
    }
    if (dp[n]) {
        return dp[n];
        // am calculat deja rezultatul pt n
    }
    long long ans = 1LL * (1LL * n * (n + 1)) / 2;
    for (int i = 2, dr; i <= n; i = dr + 1) {
        dr = (n / (n / i));
        if (dr > n) {
            break;
        }
        ans -= (dr - i + 1) * f(n / i);
    }
    dp[n] = ans;
    return ans;
}

Complexitatea algoritmului de mai sus este foarte interesantă, ea fiind O(N23)\mathcal{O}(N^\frac{2}{3}).

Problema tupleco

Kilonova

View Problem

Kilonova

Could not fetch problem details

Deschide

Se dau două numere KK și NN. Să se afle TT, numărul de tupluri formate din KK elemente (X1,X2,X3,,XK)(X_1, X_2, X_3, \dots , X_K) cu proprietatea că:

  • 1X1X2XKN1 \leq X_1 \leq X_2 \leq \dots \leq X_K \leq N.
  • cmmdc(X1,X2,,XK)=1\operatorname{cmmdc}(X_1, X_2, \dots, X_K) = 1.

Soluție de 758075 \rightarrow 80 (sau chiar 100) de puncte

Ne vom folosi de funcția Möbius pentru a calcula rezultatul. Dacă facem abstracție de prima proprietate, răspunsul nostru devine:

_d=1Nμ(d)NdK\sum\_{d=1}^{N} \mu(d) \cdot \lfloor \frac{N}{d} \rfloor ^K

Ce înseamnă însă NdK\lfloor \dfrac{N}{d} \rfloor ^ K? Reprezintă numărul de șiruri de lungime KK , unde XiX_i este multiplu de dd. Ca să numărăm doar numărul de șiruri care sunt sortate, ne vom folosi de Stars and Bars, astfel numărul de șiruri (X1,X2,X3,..,XK)(X_1, X_2, X_3, .. ,X_K) cu XiXi+1NX_i \leq X_{i+1} \leq N este egal cu (NK+1K)N-K+1 \choose K.

Rezultatul nostru devine:

d=1Nμ(d)(NdK+1K)\sum_{d=1}^{N} \mu(d) \cdot {\left\lfloor \frac{N}{d} \right\rfloor - K + 1 \choose K}

Soluția rulează în O(N)\mathcal{O}(N) cu O(N)\mathcal{O}(N) sau O(NlogN)\mathcal{O}(N \cdot \log N) precalcularea.

#include <bits/stdc++.h>
using namespace std;
const int N = 1e7 + 1, mod = 3000017;
int n, k;
ifstream fin("tupleco.in");
ofstream fout("tupleco.out");
#define cin fin
#define cout fout
long long C(int n, int k, vector<long long> &f, vector<long long> &invf) {
    return (1ULL * f[n] * (1ULL * invf[k] * invf[n - k] % mod) % mod) % mod;
}
int main() {
    cin.tie(0)->sync_with_stdio(0);
    cin >> k >> n;
    vector<long long> f(n + k + 1), inv(n + k + 1), invf(n + k + 1);
    vector<short> mobius(n + 1);
    f[0] = f[1] = inv[0] = inv[1] = invf[0] = invf[1] = 1;
    for (int i = 2; i <= n + k; i++) {
        f[i] = (1ULL * f[i - 1] * i) % mod;
        inv[i] = (1ULL * inv[mod % i] * (mod - mod / i)) % mod;
        invf[i] = (1ULL * invf[i - 1] * inv[i]) % mod;
    }

    mobius[1] = 1;
    for (int i = 1; i <= n; i++) {
        if (mobius[i]) {
            for (int j = i + i; j <= n; j += i) {
                mobius[j] -= mobius[i];
            }
        }
    }

    long long ans = 0;
    for (int d = 1; d <= n; d++) {
        int lt = n / d;
        long long plt = C(lt + k - 1, k, f, invf);
        if (mobius[d] == -1) {
            ans = (1ULL * ans + mod - plt) % mod;
        } else if (mobius[d] == 1) {
            ans = (1ULL * ans + plt) % mod;
        }
    }
    cout << ans;
}

Ok, dar putem mai bine?

Ne folosim de ideea prezentată la problema anterioară.

f(n)=(nk+1k)d=2nf(nd).f(n) = {n-k+1 \choose k} - \sum_{d=2}^{n} f\left(\left\lfloor \frac{n}{d} \right\rfloor \right).

Observație

Deducem cu puternicele noastre simțuri că modulul (MM) în problema asta este mult mai mic decât NN, astfel putem să calculăm combinările mult mai rapid:

  • nMn \leq M, deci putem precalcula combinările în O(M)\mathcal{O}(M).
  • n>Mn > M, deci (nk)% M=(nmodkmod)(nmodMkmodM) % M{n \choose k} \%\ M = {\lfloor \frac{n}{mod} \rfloor \choose \lfloor \frac{k}{mod} \rfloor} \cdot {n \bmod M \choose k \bmod M}\ \%\ M
#include <bits/stdc++.h>
using namespace std;
const int mod = 3e6 + 17, N = 1e6 + 2;
ifstream fin("tupleco.in");
ofstream fout("tupleco.out");
#define cin fin
#define cout fout
struct Mint {
    int val;
    Mint(int x = 0) { val = x % mod; }
    Mint(long long x) { val = x % mod; }
    Mint operator+(Mint oth) { return val + oth.val; }
    Mint operator*(Mint oth) { return 1LL * val * oth.val; }
    Mint operator-(Mint oth) { return val - oth.val + mod; }
    Mint fp(Mint a, long long n) {
        Mint p = 1;
        while (n) {
            if (n & 1) {
                p = p * a;
            }
            a = a * a;
            n /= 2;
        }
        return p;
    }
    Mint operator/(Mint oth) {
        Mint invers = fp(oth, mod - 2);
        return 1LL * val * invers.val;
    }
    friend ostream &operator<<(ostream &os, const Mint &lol) {
        os << lol.val;
        return os;
    }
};
vector<Mint> f(mod), invf(mod), inv(mod);
Mint C(int n, int k) {
    if (n < 0 || k < 0 || n < k) {
        return 0;
    }
    if (n >= mod) {
        return C(n / mod, k / mod) * C(n % mod, k % mod);
    }
    return f[n] * invf[n - k] * invf[k];
}
int n, k;
unordered_map<int, Mint> mp;
Mint fr(int n) {
    if (mp[n].val) {
        return mp[n];
    }
    int dr = 2;
    Mint total = C(n + k - 1, k);
    while (dr <= n) {
        int ptr = n / (n / dr);
        int lt = n / dr;
        total = total - (fr(lt) * (ptr - dr + 1));
        dr = ptr + 1;
    }
    mp[n] = total;
    return total;
}
int main() {
    f[0] = f[1] = inv[0] = inv[1] = invf[1] = invf[0] = 1;
    for (int i = 2; i < mod; i++) {
        f[i] = f[i - 1] * i;
        inv[i] = inv[mod % i] * (mod - mod / i);
        invf[i] = invf[i - 1] * inv[i];
    }

    cin >> k >> n;
    cout << fr(n);
}

Probleme suplimentare

Bibliografie și lectură suplimentară