Submission #1296739

#TimeUsernameProblemLanguageResultExecution timeMemory
1296739SamueleVidThe Big Prize (IOI17_prize)C++20
100 / 100
14 ms2184 KiB
#include <vector>
#include <cmath>
#include <cstring>
#include "prize.h" // Funzione interattiva 'ask()'
#define X first
#define Y second
using namespace std;

// Definizione di un alias per coppia di interi (per i risultati della query)
typedef pair<int, int> pii;

// Variabile globale che memorizza il massimo valore s+d trovato finora.
// Questo valore identifica il premio Meno Costoso (Lecca-Lecca),
// poiché ha il conteggio massimo di tutti gli altri premi più costosi.
int numb; 

// Array per memorizzare i risultati delle query (memoization).
// 210000 è scelto per coprire N=200000.
pii P[210000]; 

// Array booleano per tenere traccia delle scatole già interrogate.
bool mark[210000]; 

// Vettore temporaneo per memorizzare il risultato della funzione ask().
vector<int> vtmp;

/**
 * Funzione per interrogare una scatola. Utilizza la memoization.
 * @param x: Indice della scatola da interrogare.
 * @return pii: Coppia (premi_più_costosi_a_sx, premi_più_costosi_a_dx).
 */
pii query(int x) {
    // 1. Memoization: se la scatola è già stata interrogata, restituisce il risultato memorizzato.
    if (mark[x]) return P[x]; 
    
    // 2. Esecuzione della query interattiva.
    mark[x] = true;
    vtmp = ask(x); 
    
    pii tmp = pii(vtmp[0], vtmp[1]);
    
    // 3. Condizione di Trovato (Diamante): se s + d = 0, è il diamante.
    // L'uso di 'throw x' è una tecnica comune in programmazione competitiva
    // per uscire immediatamente dalla ricorsione e restituire la risposta.
    if (tmp.X + tmp.Y == 0) throw x; 
    
    // 4. Memorizzazione del risultato e ritorno.
    return P[x] = tmp;
}

/**
 * Funzione di Ricerca Binaria Adattiva.
 * Cerca un premio di riferimento (Lecca-Lecca) in un intervallo per eliminare segmenti.
 * * @param l: Limite sinistro dell'intervallo di ricerca.
 * @param r: Limite destro dell'intervallo di ricerca.
 * @param nl: Conteggio dei premi COSTOSI già identificati nell'intervallo ESTERNO SINISTRO ([0, l-1]).
 * @param nr: Conteggio dei premi COSTOSI già identificati nell'intervallo ESTERNO DESTRO ([r+1, N-1]).
 */
void bs(int l, int r, int nl, int nr) {
    if (l > r) return;
    
    // Scansione Adattiva attorno al punto medio:
    // Questo loop NON è una scansione lineare inefficiente. È un modo
    // elegante per eseguire query in una sequenza simile a [m, m-1, m+1, m-2, m+2, ...] 
    // attorno al punto centrale, al fine di trovare rapidamente una scatola di riferimento.
    for (int i = 0; i <= r - l; i++) {
        
        int midl = (l + r) / 2 - i / 2;
        int midr = (l + r) / 2 + (i + 1) / 2;
        int mid;
        
        // Alterna la scatola da interrogare tra midl e midr per "allargarsi" dal centro.
        if (i % 2 == 0) mid = midl;
        else mid = midr;

        pii tmp = query(mid);
        
        // La condizione chiave: Trovato un premio di Riferimento (Lecca-Lecca)
        // Se s + d è uguale al massimo conteggio 'numb', allora 'mid' è un lecca-lecca.
        if (tmp.X + tmp.Y == numb) {
            
            // 1. Calcolo dell'influenza del segmento di scarto:
            // Questa parte calcola quanti premi costosi si trovano nell'area
            // tra midl e midr (se fosse stata interrogata solo midl o solo midr).
            int tmpl = (i % 2 == 0 ? 0 : midr - midl); // (solo se mid = midr)
            int tmpr = (i % 2 == 1 ? 0 : midr - midl); // (solo se mid = midl)
            
            // 2. Suddivisione Ricorsiva ed Eliminazione:
            
            // Calcola i premi costosi nel segmento sinistro [l, midl-1].
            // I premi costosi 'dentro' sono: (tmp.X - premi_costosi_nel_segmento_di_scarto)
            // Se questo valore è maggiore di 'nl' (premi costosi esterni a sinistra),
            // significa che ci sono ancora premi costosi da trovare a sinistra.
            if (tmp.X - tmpl > nl) 
                // Il nuovo nr è: nr precedente + premi_costosi_che_ho_lasciato_a_destra (tmp.Y + tmpl)
                bs(l, midl - 1, nl, tmp.Y + tmpl);

            // Calcola i premi costosi nel segmento destro [midr+1, r].
            // Stessa logica per la parte destra: se il conteggio normalizzato
            // è maggiore di 'nr' (premi costosi esterni a destra).
            if (tmp.Y - tmpr > nr) 
                // Il nuovo nl è: nl precedente + premi_costosi_che_ho_lasciato_a_sinistra (tmp.X + tmpr)
                bs(midr + 1, r, tmp.X + tmpr, nr);
            
            // Trovato il punto di riferimento, abbiamo diviso il problema. Usciamo dal loop.
            break; 
        }
    }
}

/**
 * Funzione principale per trovare il diamante.
 * @param n: Dimensione totale della riga.
 * @return int: Indice del diamante.
 */
int find_best(int n) {
    if (n == 1) return 0;
    
    // Usa un blocco try-catch per catturare il 'throw x' quando il diamante è trovato.
    try {
        numb = 0;
        // Inizializza i flag di memoization
        memset(mark, false, sizeof mark); 
        int p = 0;
        
        // Fase 1: Pre-ricerca del Lecca-Lecca (Premio di Riferimento)
        // Esegue O(sqrt(n)) query iniziali. (475 per N=200000).
        // Cerca il massimo valore di s+d (numb) per identificare il lecca-lecca.
        for (int i = 0; i < sqrt(n) + 30 && i < n && numb <= 26; i++) {
            pii tmp = query(i);
            if (tmp.X + tmp.Y > numb) p = i; // p è l'indice del lecca-lecca (o un premio vicino)
            numb = max(numb, tmp.X + tmp.Y);
        }
        
        // Fase 2: Ricerca Binaria sul resto della riga.
        // Inizia la ricerca binaria dal lecca-lecca 'p' fino alla fine 'n-1'.
        // I parametri nl=p e nr=0 indicano che 'p' scatole sono a sinistra,
        // ma si sta lavorando sull'intervallo [p, n-1].
        // La gestione dei parametri 'nl' e 'nr' qui è sottile e varia a seconda dell'implementazione.
        bs(p, n - 1, p, 0); 
    } 
    catch (int ans) {
        // Cattura l'indice del diamante lanciato da query()
        return ans;
    }
    
    // Se la ricerca fallisce (non dovrebbe), restituisce -1.
    return -1;
}
#Verdict Execution timeMemoryGrader output
Fetching results...
#Verdict Execution timeMemoryGrader output
Fetching results...