Fractal graphs 90/100

allora ho fatto due codici, il primo fatto totalmente da me che fa 90/100 ovvero il seguente:

#include <bits/stdc++.h>
using namespace std;
using ll = long long int;
#define mod 32749

void fast() {
    ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL);
}

void print(vector<int> const &input){ //se non usi vector<int> cambialo
    for (int i = 0; i < input.size(); i++) cout << input.at(i) << ' ';
}

ll nodi = 1, archi = 0; 
//triangolo = +2 nodi, +3 archi
//segmento = +2 nodi, +2 archi
//inserire nodi triangoli e a segmenti

void fractal_graph(ll n){
    nodi %= mod; archi %= mod;
    if(n == 0) {
        cout << nodi << " " << archi;
        return;
    }
    ll nodi_triangoli = (nodi * 2) % mod, archi_triangoli = (nodi * 3) % mod, nodi_segmenti = (archi * 2) % mod, archi_segmenti = (archi * 2) % mod;
    nodi +=( nodi_triangoli + nodi_segmenti) % mod;
    archi += (archi_triangoli + archi_segmenti) % mod;
    fractal_graph(n-1);
}

int main(){
    fast();
    freopen("input.txt", "r", stdin);
    freopen("output.txt", "w", stdout);

    ll n; cin >> n;

    fractal_graph(n);

    return 0;
}

questo non riesce a fare gli ultimi testcase ovviamente per TLE, essendo che quando N > 50 000 000 l’algoritmo fa molta fatica visto che fa tante chiamate quanto è N

Mentre l’altro vedendo un pò qualche risposta sul forum riguardo questo problema, l’ho fatto scopiazzando qualche formula ovvero:

#include <bits/stdc++.h>
using namespace std;
using ll = long long int;
#define mod 32749

void fast() {
    ios_base::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL);
}

void print(vector<int> const &input){ //se non usi vector<int> cambialo
    for (int i = 0; i < input.size(); i++) cout << input.at(i) << ' ';
}

ll nodi = 1, archi = 0;
//triangolo = +2 nodi, +3 archi
//segmento = +2 nodi, +2 archi
//inserire nodi triangoli e a segmenti

void fractal_graph(ll n){
    if(n == 0) return;
    
    fractal_graph(n-1);
    ll temp = nodi;
    nodi = (nodi * 3) % mod + (archi * 2) % mod;
    archi = (temp * 3) % mod + (archi * 3) % mod;
    nodi %= mod;
    archi %= mod;

    return;


}

int main(){
    fast();
    freopen("input.txt", "r", stdin);
    freopen("output.txt", "w", stdout);
    ll n; cin >> n;

    fractal_graph(n);
    
    cout<<nodi<<" "<<archi;
    return 0;
}
    

Questo fa 70/100 solo che non fa i testcase per “limite di memoria superato” però sembra che a velocità ci siamo:


Quindi vi chiedo:

  1. perchè il problema che fa 70/100 supera i limiti di memoria, e perchè anche avendo la stessa complessità del mio, è più veloce (mi viene da pensare che termina appena supera il limite e quindi non finisce tutte le chiamate però non so)
  2. c’è necessariamente bisogno di una formula matematica generale? se si come devo ragionare per tirarla fuori o comunque per induzione generarla dai casi più piccoli?
  3. è giusto spammare un pò ovunque i %mod? per stare sicuro li ho messi ovunque ma non so se possono essere un problema
1 Mi Piace
  1. Il tuo programma viene killato non appena raggiunge il limite di memoria.
  2. Con un po’ di teoria sulle ricorrenze lineari puoi tirare un formula chiusa abbastanza brutta ma calcolabile in O(\log N), tuttavia per risolvere le ricorrenze lineari di solito si usa una tecnica particolare, cioè esponenziazione di matrici (trovi una spiegazione a pagina 220 della bibbia).
  3. Sì i moduli vanno virtualmente ovunque se no rischi di overfloware.

grazie mille per l’aiuto, ci sono arrivato dopo un pò che bastava elevare la matrice per n, non hai idea dei video sulle successioni, matrici, autovalori, autovettori, stavo impazzendo, forse dovrei leggere meglio i consigli… Comunque sono soddisfatto di avercela fatta, alla fine conoscevo già la funzione di esponenziazione veloce, quindi mi è bastato capire come moltiplicare due matrici , essendo che in matematica non lo avevo mai fatto, ma implementarlo non è stato difficile… daje
questo è il codice giusto btw:

vector<vector<int>> multiply(const vector<vector<int>> &a, const vector<vector<int>> &b) {

    vector<vector<int>> result (2, vector<int>(2));

    for(int i = 0; i < 2; ++i){
        for(int j = 0; j < 2; ++j){
            for(int k = 0; k < 2; ++k){
                result[i][j] += (a[i][k] * b[k][j]);
                result[i][j] %= mod;
            }
        }
    }

    return result;
}


vector<vector<int>> fastexp(const vector<vector<int>> &a, int b){
    if (b == 0) return {{1, 0}, {0, 1}};
    vector<vector<int>> x = fastexp(a, b/2);
    if(b % 2 == 0) return multiply(x, x);
    return multiply(multiply(x, x), a);
}


int main() {
    fast();

    int n; cin>>n;

    vector<vector<int>> sol = fastexp({{3, 2}, {3, 3}}, n);

    cout << sol[0][0] << " " << sol[1][0] << endl;

    return 0;
}

grazie di nuovo per l’aiuto

Di nulla. Visto che hai menzionato gli autovalori/autovettori descrivo anche una soluzione un po’ più complessa che ne fa uso (ma che, sottolineo, è abbastanza inutile).

Come hai visto il problema chiede di calcolare M^N con M=\begin{pmatrix} 3 & 2\\ 3 & 3 \end{pmatrix}. Invece di calcolare direttamente questa quantità con esponenziazione veloce, possiamo per prima cosa “spostarci” nella base definita da due autovettori di M.

Lascio a te verificare che \begin{pmatrix} 2\\ \sqrt6 \end{pmatrix} e \begin{pmatrix} 2\\ -\sqrt6 \end{pmatrix} sono due autovettori linearmente indipendenti di M.

Possiamo allora trovare l’equivalente della nostra matrice nella nuova base calcolando
M'=\begin{pmatrix} 2 & 2\\ \sqrt6 & -\sqrt6 \end{pmatrix}^{-1}\begin{pmatrix} 3 & 2\\ 3 & 3 \end{pmatrix}\begin{pmatrix} 2 & 2\\ \sqrt6 & -\sqrt6 \end{pmatrix}=\begin{pmatrix} 3+\sqrt6 & 0\\ 0 & 3-\sqrt6 \end{pmatrix}.

Come atteso, M' è diagonale e pertanto per elevarla all’N-esima potenza basta elevare singolarmente tutti gli elementi lungo la sua diagonale.

Abbiamo dunque trovato che (M')^N = \begin{pmatrix} (3+\sqrt6)^N & 0\\ 0 & (3-\sqrt6)^N \end{pmatrix}, per ottenere la matrice M^N basta a questo punto invertire il cambio di base ottenendo:
\begin{align*} M^N &= \begin{pmatrix} 2 & 2\\ \sqrt6 & -\sqrt6 \end{pmatrix} \begin{pmatrix} (3+\sqrt6)^N & 0\\ 0 & (3-\sqrt6)^N \end{pmatrix}\begin{pmatrix} 2 & 2\\ \sqrt6 & -\sqrt6 \end{pmatrix}^{-1} \\ &=\begin{pmatrix} \frac{1}{2}[(3+\sqrt6)^N + (3-\sqrt6)^N] & \frac{\sqrt6}{6}[(3+\sqrt6)^N - (3-\sqrt6)^N] \\ \frac{\sqrt6}{4}[(3+\sqrt6)^N - (3-\sqrt6)^N] & \frac{1}{2}[(3+\sqrt6)^N + (3-\sqrt6)^N] \end{pmatrix} \end{align*}

Ora basta implementare fastexp sui numeri della forma a+b\sqrt6 per calcolare in \mathcal{O}(\log N) le quantità richieste.

Lascio un esempio di implementazione:
#include <iostream>
using namespace std;

constexpr int MOD = 32749;

struct Num {
    int a, b;
    
    Num(int a, int b) : a(a), b(b) {}
    Num operator*(const Num &o) const {
        return Num((a * o.a + b * o.b % MOD * 6) % MOD, (a * o.b + o.a * b) % MOD);
    }
};

Num fexp(Num b, int e) {
    Num a(1, 0);
    do {
        if (e & 1) a = a * b;
        b = b * b;
    } while (e >>= 1);
    return a;
}

int main() {
    int N; cin >> N;
    
    Num x = fexp(Num(3, 1), N);
    Num y = fexp(Num(3, MOD - 1), N);
    
    int A = (x.a + y.a) * ((MOD + 1) / 2) % MOD;
    int B = 3LL * (x.b - y.b + MOD) * ((MOD + 1) / 2) % MOD;
    
    cout << A << ' ' << B << '\n';
}
2 Mi Piace