2013-01-13 20 views
7

che sto cercando di risolvere un problema giudice on-line: http://opc.iarcs.org.in/index.php/problems/LEAFEATpiù veloce algoritmo per trovare quanti numeri non sono divisibili per un determinato insieme di numeri

Il problema in breve:

Se ci viene dato un intero L e un insieme di N interi s1, s2, s3..sN, dobbiamo trovare quanti numeri ci sono da 0 a L-1 che non sono divisibili per nessuno dei 'si'.

Ad esempio, se ci sono dati, L = 20 e S = {3,2,5} poi ci sono 6 numeri da 0 a 19 che non sono divisibili per 3,2 o 5.

L < = 1000000000 e N = 20. <

ho usato il principio di inclusione-esclusione per risolvere questo problema:

/*Let 'T' be the number of integers that are divisible by any of the 'si's in the 
given range*/ 

for i in range 1 to N 
    for all subsets A of length i 
    if i is odd then: 
     T += 1 + (L-1)/lcm(all the elements of A) 
    else 
     T -= 1 + (L-1)/lcm(all the elements of A) 
return T 

Ecco il mio codice per risolvere questo problema

#include <stdio.h> 

int N; 
long long int L; 
int C[30]; 

typedef struct{int i, key;}subset_e; 
subset_e A[30]; 
int k; 

int gcd(a,b){ 
int t; 
    while(b != 0){ 
      t = a%b; 
      a = b; 
      b = t; 
    } 

    return a; 
} 

long long int lcm(int a, int b){ 
    return (a*b)/gcd(a,b); 
} 

long long int getlcm(int n){ 
    if(n == 1){ 
    return A[0].key; 
    } 
    int i; 
    long long int rlcm = lcm(A[0].key,A[1].key); 
    for(i = 2;i < n; i++){ 
    rlcm = lcm(rlcm,A[i].key); 
    } 
    return rlcm; 
} 

int next_subset(int n){ 
    if(k == n-1 && A[k].i == N-1){ 
    if(k == 0){ 
     return 0; 
    } 
    k--; 
    } 
    while(k < n-1 && A[k].i == A[k+1].i-1){ 
    if(k <= 0){ 
     return 0; 
    } 
    k--; 
    } 
    A[k].key = C[A[k].i+1]; 
    A[k].i++; 
    return 1; 
} 

int main(){ 
    int i,j,add; 
    long long int sum = 0,g,temp; 
    scanf("%lld%d",&L,&N); 
    for(i = 0;i < N; i++){ 
    scanf("%d",&C[i]); 
    } 
    for(i = 1; i <= N; i++){ 
    add = i%2; 
    for(j = 0;j < i; j++){ 
     A[j].key = C[j]; 
     A[j].i = j; 
    } 
    temp = getlcm(i); 
    g = 1 + (L-1)/temp; 
    if(add){ 
     sum += g; 
    } else { 
     sum -= g; 
    } 
    k = i-1; 
    while(next_subset(i)){ 
     temp = getlcm(i); 
     g = 1 + (L-1)/temp; 
     if(add){ 
     sum += g; 
     } else { 
     sum -= g; 
     } 
    } 
    } 
    printf("%lld",L-sum); 
    return 0; 
} 

Il next_subset(n) genera la prossima sottoinsieme di dimensione n della matrice A, se non ci sono sottoinsieme restituisce 0 in caso contrario restituisce 1. Si basa su l'algoritmo descritto dalla risposta accettata in this StackOverflow questione.

La funzione lcm(a,b) restituisce l'lcm di aeb. La funzione get_lcm(n) restituisce l'lcm di tutti gli elementi in A. Utilizza la proprietà: LCM(a,b,c) = LCM(LCM(a,b),c)

Quando invio il problema al giudice, il mio limite di tempo è superato. Se risolviamo questo usando la forza bruta otteniamo solo il 50% dei voti.

Come può esserci fino a 2^20 sottoinsiemi il mio algoritmo potrebbe essere lento, quindi ho bisogno di un algoritmo migliore per risolvere questo problema.

EDIT:

Dopo aver modificato il mio codice e cambiando la funzione per l'algoritmo di Euclide, sto ottenendo una risposta sbagliata, ma il mio codice viene eseguito entro il tempo limite. Mi dà una risposta corretta al test di esempio ma non a nessun altro caso di test; ecco un link a ideone dove ho eseguito il mio codice, il primo output è corretto ma il secondo no.

Il mio approccio a questo problema è corretto? Se è allora ho commesso un errore nel mio codice e lo troverò; altrimenti qualcuno può spiegare cosa c'è che non va?

+1

+1 per aver fornito questo sito con così tanti problemi interessanti. Al lavoro! –

+1

La funzione lcm e gcd dovrebbe richiedere a, b quanto a lungo. – nhahtdh

risposta

2

Si potrebbe anche provare a modificare la propria funzione lcm per utilizzare Euclidean algorithm.

int gcd(int a, int b) { 
    int t; 

    while (b != 0) { 
     t = b; 
     b = a % t; 
     a = t; 
    } 

    return a; 
} 

int lcm(int a, int b) { 
    return (a * b)/gcd(a, b); 
} 

Almeno con Python, le differenze di velocità tra i due sono abbastanza grande:

>>> %timeit lcm1(103, 2013) 
100000 loops, best of 3: 9.21 us per loop 
>>> %timeit lcm2(103, 2013) 
1000000 loops, best of 3: 1.02 us per loop 
+0

grazie per la tua risposta – 2147483647

+0

Dopo aver implementato il metodo che hai descritto, entro il limite di tempo ma ricevo una risposta sbagliata, modifico la domanda e aggiungo il nuovo codice – 2147483647

+1

@ A.06: vedi la mia risposta per la C traduzione. Probabilmente funzionerà. – Blender

1

In genere, il minimo comune multiplo di un sottoinsieme di k del s_i supererà L per k molto più piccolo di 20. Quindi è necessario smettere presto.

Probabilmente, basta inserire

if (temp >= L) { 
    break; 
} 

dopo

while(next_subset(i)){ 
    temp = getlcm(i); 

sarà sufficiente.

Inoltre, scorciatoia se ci sono 1 s tra i s_i, tutti i numeri sono divisibili per 1.

Penso che la seguente sarà più veloce:

unsigned gcd(unsigned a, unsigned b) { 
    unsigned r; 
    while(b) { 
     r = a%b; 
     a = b; 
     b = r; 
    } 
    return a; 
} 

unsigned recur(unsigned *arr, unsigned len, unsigned idx, unsigned cumul, unsigned bound) { 
    if (idx >= len || bound == 0) { 
     return bound; 
    } 
    unsigned i, g, s = arr[idx], result; 
    g = s/gcd(cumul,s); 
    result = bound/g; 
    for(i = idx+1; i < len; ++i) { 
     result -= recur(arr, len, i, cumul*g, bound/g); 
    } 
    return result; 
} 

unsigned inex(unsigned *arr, unsigned len, unsigned bound) { 
    unsigned i, result = bound, t; 
    for(i = 0; i < len; ++i) { 
     result -= recur(arr, len, i, 1, bound); 
    } 
    return result; 
} 

chiamano con

unsigned S[N] = {...}; 
inex(S, N, L-1); 

Non è necessario aggiungere il 1 per lo 0 in qualsiasi punto, poiché 0 è divisibile per tutti i numeri, calcolare il conteggio dei numeri 1 <= k < L che non sono divisibili da alcun s_i.

+0

Dopo aver provato il metodo, non c'è molta differenza, ho il tempo limite superato – 2147483647

+1

Ah, peccato. Ma non ho nemmeno guardato all'implementazione 'lcm', se si utilizza l'algoritmo Euclideo, come suggerisce Blender, dovrebbe renderlo significativamente più veloce in media. E proporrò un altro approccio all'inclusione-esclusione, che credo sarà più veloce del tuo 'next_subset'. –

+0

@ A.06 Ci è voluto un po 'di tempo per correggere un errore off-by-one, mi dispiace. –

1

temo il problema comprensione non è forse corretto.

Hai L. Hai un set S di elementi K. Devi contare la somma del quoziente di L/Si. Per L = 20, K = 1, S = {5}, la risposta è semplicemente 16 (20 - 20/5). Ma K> 1, quindi devi considerare anche i multipli comuni.

Perché eseguire il ciclo di un elenco di sottoinsiemi? Non riguarda il calcolo del sottoinsieme, solo la divisione e il multiplo.

Hai K interi distinti. Ogni numero potrebbe essere un numero primo. Devi considerare multipli comuni. È tutto.

EDIT

L = 20 e S = {3,2,5}

foglie possono essere mangiati da 3 = 6
Foglie potrebbe essere mangiato da 2 = 10
Foglie potrebbe essere mangiato da 5 = 4 multipli

comuni di S, rispetto ad L, non in S = 6, 10, 15

foglie realtà mangiato = 20/3 + 20/2 + 20/5 - 20/6 - 20/10 - 20/15 = 6

+0

Posso risolverlo in un altro modo, ma tutti implicano cicli di L volte che non sono abbastanza veloci a causa delle dimensioni di K. – 2147483647

+0

Il problema vuole che tu studi la teoria dei numeri interi non la teoria degli insiemi. Design del ciclo, l'algoritmo per il ciclo non è il punto. – 9dan

+0

Non ho capito cosa intendi, ma se eseguiamo un loop L volte ovunque nel nostro codice otteniamo un TLE, l'ho provato. – 2147483647

1

Creare un array di flag con le voci L. Quindi contrassegnare ogni foglia toccato:

for(each size in list of sizes) { 
    length = 0; 
    while(length < L) { 
     array[length] = TOUCHED; 
     length += size; 
    } 
} 

poi trovare le foglie intatte:

for(length = 0; length < L; length++) { 
    if(array[length] != TOUCHED) { /* Untouched leaf! */ } 
} 

Nota che non c'è la moltiplicazione e la divisione in questione; ma avrai bisogno di circa 1 GB di RAM. Se la RAM è un problema, puoi utilizzare una serie di bit (massimo 120 MiB).

Questo è solo un inizio, poiché ci sono schemi ripetuti che possono essere copiati anziché generati. Il primo modello va da 0 a S1 * S2, il successivo è da 0 a S1 * S2 * S3, il successivo è da 0 a S1 * S2 * S3 * S4, ecc.

In pratica, è possibile impostare tutti i valori toccati da S1 e poi S2 da 0 a S1 * S2; quindi copia il pattern da 0 a S1 * S2 fino a S1 * S2 * S3 e imposta tutti gli S3 tra S3 e S1 * S2 * S3; quindi copia quel modello fino ad arrivare a S1 * S2 * S3 * S4 e imposta tutti gli S4 tra S4 e S1 * S2 * S3 * S4 e così via.

Avanti; se S1 * S2 * ... Sn è minore di L, sai che il modello si ripeterà e può generare i risultati per lunghezze da S1 * S2 * ... Sn a L dal modello. In questo caso, la dimensione dell'array deve essere S1 * S2 * ... Sn e non deve essere L.

Infine, se S1 * S2 * ... Sn è maggiore di L; quindi è possibile generare il modello per S1 * S2 * ... (Sn-1) e utilizzare tale modello per creare i risultati da S1 * S2 * ... (Sn-1) a S1 * S2 * ... Sn. In questo caso se S1 * S2 * ... (Sn-1) è più piccolo di L, allora l'array non deve essere grande come L.

+0

Grazie ma ho già provato anche questo, l'ho fatto con un bitset, ma la dimensione di L non ha anche i limiti di memoria del problema è 3mb o qualcosa – 2147483647

+0

Heh - OK - lasciami provare qualcos'altro allora! :-) – Brendan

1

Puoi tenere traccia della distanza fino a quella successiva toccata per ogni taglia La distanza dalla prossima foglia toccata sarà la distanza più piccola, e sottrai questa distanza da tutte le altre (e avvolgi quando la distanza è zero).

Ad esempio:

int sizes[4] = {2, 5, 7, 9}; 
int distances[4]; 
int currentLength = 0; 

for(size = 0 to 3) { 
    distances[size] = sizes[size]; 
} 

while(currentLength < L) { 
    smallest = INT_MAX; 
    for(size = 0 to 3) { 
     if(distances[size] < smallest) smallest = distances[size]; 
    } 
    for(size = 0 to 3) { 
     distances[size] -= smallest; 
     if(distances[size] == 0) distances[size] = sizes[size]; 
    } 
    while((smallest > 1) && (currentLength < L)) { 
     currentLength++; 
     printf("%d\n", currentLength; 
     smallest--; 
    } 
} 
+0

grazie per la tua risposta. – 2147483647

1

@ A.06: u r quello con nome utente linkinmew su OPC, rito?

In ogni caso, la risposta richiede solo che tu crei tutti i possibili sottoinsiemi e quindi applichi il principio di esclusione dell'inclusione. Questo cadrà bene entro i limiti di tempo per i dati forniti. Per creare tutti i possibili sottoinsiemi, puoi facilmente definire una funzione ricorsiva.

+0

sì, io sono linkinmew – 2147483647

+1

Spero che tu possa provare il meglio a risolvere i problemi da solo prima di chiedere, dal momento che tu hai chiesto quasi ogni buona domanda su opc. Saluti! – Somebody

Problemi correlati