2016-12-17 4 views
0

Ich habe ein Problem:C++ - Code-Optimierung

Sie eine Sequenz gegeben sind, in Form einer Zeichenkette mit Zeichen ‚0‘, ‚1‘, und nur ‚?‘. Angenommen, es gibt k '?' S. Dann gibt es 2^k Wege, jedes '?' Durch eine '0' oder eine '1' zu ersetzen, was 2^k verschiedene 0-1 Sequenzen ergibt (0-1 Sequenzen sind Sequenzen mit nur Nullen und Einsen).

Definieren Sie für jede 0-1-Sequenz die Anzahl der Inversionen als Mindestanzahl an benachbarten Swaps, die zum Sortieren der Sequenz in nicht abnehmender Reihenfolge erforderlich sind. Bei diesem Problem wird die Sequenz genau in nicht abnehmender Reihenfolge sortiert, wenn alle Nullen vor allen Einsen auftreten. Zum Beispiel hat die Sequenz 11010 5 Inversionen. Wir können es durch die folgenden Schritte sortieren: 11010 →→ 11001 →→ 10101 →→ 01101 →→ 01011 →→ 00111.

Finden Sie die Summe der Anzahl von Umkehrungen der 2^k Sequenzen, Modulo 1000000007 (10^9 + 7).

Zum Beispiel:

Eingang: ?? 01 -> Ausgabe: 5

Eingabe: 0? -> Ausgang: 3

Hier ist mein Code:

#include <iostream> 
#include <stdio.h> 
#include <stdlib.h> 
#include <string> 
#include <string.h> 
#include <math.h> 

using namespace std; 



void ProcessSequences(char *input) 
{ 
int c = 0; 

/* Count the number of '?' in input sequence 
* 1??0 -> 2 
*/ 
for(int i=0;i<strlen(input);i++) 
{ 
    if(*(input+i) == '?') 
    { 
     c++; 
    }  
} 


/* Get all possible combination of '?' 
* 1??0 
* -> ?? 
* -> 00, 01, 10, 11 
*/ 
int seqLength = pow(2,c); 
// Initialize 2D array of integer 
int **sequencelist, **allSequences; 
sequencelist = new int*[seqLength]; 
allSequences = new int*[seqLength]; 
for(int i=0; i<seqLength; i++){ 
    sequencelist[i] = new int[c]; 
    allSequences[i] = new int[500000]; 
} 
//end initialize 

for(int count = 0; count < seqLength; count++) 
{ 
    int n = 0; 
    for(int offset = c-1; offset >= 0; offset--) 
    { 
     sequencelist[count][n] = ((count & (1 << offset)) >> offset); 
     // cout << sequencelist[count][n]; 
     n++; 
    } 
    // cout << std::endl; 
} 

/* Change '?' in former sequence into all possible bits 
* 1??0 
* ?? -> 00, 01, 10, 11 
* -> 1000, 1010, 1100, 1110 
*/ 
for(int d = 0; d<seqLength; d++) 
{ 
    int seqCount = 0; 
    for(int e = 0; e<strlen(input); e++) 
    { 
     if(*(input+e) == '1') 
     { 
      allSequences[d][e] = 1; 
     } 
     else if(*(input+e) == '0') 
     { 
      allSequences[d][e] = 0; 
     } 
     else 
     { 
      allSequences[d][e] = sequencelist[d][seqCount]; 
      seqCount++; 
     } 
    } 
} 


/* 
* Sort each sequences to increasing mode 
* 
*/ 
// cout<<endl; 
int totalNum[seqLength]; 
for(int i=0; i<seqLength; i++){ 
    int num = 0; 
    for(int j=0; j<strlen(input); j++){ 
     if(j==strlen(input)-1){ 
      break; 
     } 
     if(allSequences[i][j] > allSequences[i][j+1]){ 
      int temp = allSequences[i][j]; 
      allSequences[i][j] = allSequences[i][j+1]; 
      allSequences[i][j+1] = temp; 
      num++; 
      j = -1; 
     }//endif 
    }//endfor 
    totalNum[i] = num; 
}//endfor 





/* 
* Sum of all Num of Inversions 
*/ 
int sum = 0; 
for(int i=0;i<seqLength;i++){ 
    sum = sum + totalNum[i]; 
} 


// cout<<"Output: "<<endl; 
int out = sum%1000000007; 
cout<< out <<endl; 


} //end of ProcessSequences method 


int main() 
{ 
    // Get Input 
    char seq[500000]; 
    // cout << "Input: "<<endl; 
    cin >> seq; 

    char *p = &seq[0]; 

    ProcessSequences(p); 
    return 0; 
} 

die Ergebnisse direkt für kleine Größe eingegeben wurden, aber für größere Größe Eingabe habe ich Zeit CPU Zeitlimit> 1 Sekunde. Ich habe auch Speichergröße überschritten. Wie man es schneller und optimalen Speicherverbrauch macht? Welchen Algorithmus sollte ich verwenden und welche bessere Datenstruktur sollte ich verwenden? Danke.

+0

Sie zeigen Ihren Code in der Frage nicht (so ist Ihre Frage sehr unklar). Und wenn Sie Ihren Code zeigen würden, würde Ihre Frage zu einer * fix-my-code * -Frage werden. –

+0

@BasileStarynkevitch Aktualisiert, sorry, ich habe vergessen, meinen Code zu posten. –

+0

@BasileStarynkevitch, ach ja, es gibt eine Frage zu meinem festen Code, sorry, wusste das nicht. –

Antwort

1

Dynamische Programmierung ist der richtige Weg. Stellen Sie sich vor Sie fügen allen Sequenzen das letzte Zeichen hinzu.

  • Wenn es 1 ist, dann erhalten Sie XXXXXX1. Die Anzahl der Swaps ist offensichtlich dieselbe wie für jede Sequenz bisher.
  • Wenn es 0 ist, dann müssen Sie die Anzahl der bereits in jeder Sequenz wissen.Die Anzahl der Swaps würde für jede Sequenz um die Anzahl der Einsen zunehmen.
  • Wenn es ? Sie fügen nur zwei früheren Fällen zusammen

Sie müssen berechnen, wie viele Sequenzen sind. Für jede Länge und für jede Anzahl von Einsen (die Anzahl der Einsen in der Sequenz kann natürlich nicht größer sein als die Länge der Sequenz). Sie beginnen mit der Länge 1, was trivial ist, und mit länger fortfahren. Sie können wirklich große Zahlen bekommen, also sollten Sie modulo 1000000007 die ganze Zeit berechnen. Das Programm ist nicht in C++, sollte aber leicht zu schreiben sein (Array sollte auf 0 initialisiert werden, int ist 32bit lang in 64bit).

long Mod(long x) 
{ 
    return x % 1000000007; 
} 

long Calc(string s) 
{ 
    int len = s.Length; 
    long[,] nums = new long[len + 1, len + 1]; 
    long sum = 0; 
    nums[0, 0] = 1; 

    for (int i = 0; i < len; ++i) 
    { 
     if(s[i] == '?') 
     { 
      sum = Mod(sum * 2); 
     } 
     for (int j = 0; j <= i; ++j) 
     { 
      if (s[i] == '0' || s[i] == '?') 
      { 
       nums[i + 1, j] = Mod(nums[i + 1, j] + nums[i, j]); 
       sum = Mod(sum + j * nums[i, j]); 
      } 

      if (s[i] == '1' || s[i] == '?') 
      { 
       nums[i + 1, j + 1] = nums[i, j]; 
      } 
     } 
    } 

    return sum; 
} 

Optimalisierung

Der obige Code geschrieben ist so klar wie möglich zu sein und die dynamische Programmierung Ansatz zu zeigen. Sie brauchen Array [len+1, len+1] nicht wirklich. Sie berechnen Spalte i+1 aus Spalte i und gehen nie zurück, so zwei Spalten sind genug - alt und neu. Wenn Sie mehr darüber erfahren, finden Sie heraus, dass die Zeile j der neuen Spalte nur von Zeile j und j-1 der alten Spalte abhängt. Sie können also mit einer Spalte fortfahren, wenn Sie die Werte in die richtige Richtung bringen (und Werte nicht überschreiben, die Sie benötigen).

Der obige Code verwendet 64-Bit-Ganzzahlen. Sie brauchen das wirklich nur in j * nums[i, j]. Das nums Array enthält Zahlen von weniger als 1000000007 und 32bit Integer ist ausreichend. Sogar 2 * 1000000007 kann in 32bit signed int passen, wir können davon Gebrauch machen.

Wir können den Code optimieren, indem wir Schleifen in Bedingungen statt in Bedingungen in der Schleife verschachteln. Vielleicht ist es noch natürlicher Ansatz, der einzige Nachteil ist die Wiederholung des Codes.

Der% Operator ist, wie jede Division, ziemlich teuer. j * nums[i, j] ist typischerweise viel kleiner als 64-Bit-Integer, so dass wir nicht in jedem Schritt modulo machen müssen. Einfach den tatsächlichen Wert beobachten und bei Bedarf anwenden. Die Mod(nums[i + 1, j] + nums[i, j]) kann auch optimiert werden, da nums[i + 1, j] + nums[i, j] immer kleiner als 2 * 1000000007 wäre.

Und schließlich der optimierte Code. Ich wechselte zu C++, erkannte ich, gibt es Unterschiede, was int und long Mittel, also eher machen deutlich:

long CalcOpt(string s) 
{ 
    long len = s.length(); 
    vector<long> nums(len + 1); 
    long long sum = 0; 
    nums[0] = 1; 
    const long mod = 1000000007; 

    for (long i = 0; i < len; ++i) 
    { 
     if (s[i] == '1') 
     { 
      for (long j = i + 1; j > 0; --j) 
      { 
       nums[j] = nums[j - 1]; 
      } 
      nums[0] = 0; 
     } 
     else if (s[i] == '0') 
     { 
      for (long j = 1; j <= i; ++j) 
      { 
       sum += (long long)j * nums[j]; 
       if (sum > std::numeric_limits<long long>::max()/2) { sum %= mod; } 
      } 
     } 
     else 
     { 
      sum *= 2; 
      if (sum > std::numeric_limits<long long>::max()/2) { sum %= mod; } 
      for (long j = i + 1; j > 0; --j) 
      { 
       sum += (long long)j * nums[j]; 
       if (sum > std::numeric_limits<long long>::max()/2) { sum %= mod; } 
       long add = nums[j] + nums[j - 1]; 
       if (add >= mod) { add -= mod; } 
       nums[j] = add; 
      } 
     } 
    } 

    return (long)(sum % mod); 
} 

Vereinfachung

Zeitlimit noch überschritten? Es gibt wahrscheinlich einen besseren Weg, es zu tun. Sie können entweder

  1. an den Anfang zurück und mathematisch andere Art und Weise herauszufinden, das Ergebnis
  2. oder vereinfachen tatsächliche Lösung mit Mathe

ich den zweiten Weg gegangen zu berechnen. Was wir in der Schleife tun, ist in der Tat Faltung von zwei Sequenzen, zum Beispiel:

0, 0, 0, 1, 4, 6, 4, 1, 0, 0,... and 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,... 
0*0 + 0*1 + 0*2 + 1*3 + 4*4 + 6*5 + 4*6 + 1*7 + 0*8...= 80 

Die erste Sequenz ist symmetrisch und die zweite ist linear.In diesem Fall kann die Summe der Faltung aus der Summe der ersten Sequenz berechnet werden, die = 16 (numSum) ist, und der Nummer aus der zweiten Sequenz, die der Mitte der ersten Sequenz entspricht, die 5 ist (numMult). numSum*numMult = 16*5 = 80. Wir ersetzen die ganze Schleife durch eine Multiplikation, wenn wir in der Lage sind, diese Zahlen in jedem Schritt zu aktualisieren, was wohl der Fall ist.

Wenn s [i] == '0' dann ändert sich numSum nicht und numMult ändert sich nicht.

Wenn s [i] == '1' dann ändert sich numSum nicht, nur numMult wird um 1 erhöht, da wir die gesamte Sequenz um eine Position verschieben.

Wenn s [i] == '?' wir fügen die ursprüngliche und die verschobene Sequenz zusammen hinzu. numSum wird mit 2 und numMult mit 0,5 multipliziert.

Die 0,5 bedeutet ein bisschen Problem, da es nicht die ganze Zahl ist. Aber wir wissen, dass das Ergebnis eine ganze Zahl sein würde. Glücklicherweise existiert bei der modularen Arithmetik in diesem Fall die Inversion von zwei (= 1/2) als eine ganze Zahl. Es ist h = (mod + 1)/2. Zur Erinnerung, Umkehrung von 2 ist eine solche Zahl, dass h * 2 = 1 modulo mod. Implementierung weise ist es einfacher, numMult mit 2 zu multiplizieren und numSum durch 2 zu teilen, aber es ist nur ein Detail, wir würden sowieso 0,5 benötigen. Der Code:

long CalcOptSimpl(string s) 
{ 
    long len = s.length(); 
    long long sum = 0; 
    const long mod = 1000000007; 
    long numSum = (mod + 1)/2; 
    long long numMult = 0; 

    for (long i = 0; i < len; ++i) 
    { 
     if (s[i] == '1') 
     { 
      numMult += 2; 
     } 
     else if (s[i] == '0') 
     { 
      sum += numSum * numMult; 
      if (sum > std::numeric_limits<long long>::max()/4) { sum %= mod; } 
     } 
     else 
     { 
      sum = sum * 2 + numSum * numMult; 
      if (sum > std::numeric_limits<long long>::max()/4) { sum %= mod; } 

      numSum = (numSum * 2) % mod; 
      numMult++; 
     } 
    } 

    return (long)(sum % mod); 
} 

Ich bin ziemlich sicher, es gibt einige einfache Möglichkeit, diesen Code zu bekommen, aber ich bin immer noch nicht in der Lage, es zu sehen. Aber manchmal ist Pfad das Ziel :-)

+0

hei, es funktioniert. Ich werde die Ausführungszeit auswerten. –

+0

aber es Speicherlimit überschritten hat, werde ich versuchen, bigintegers zu verwenden. –

+0

Der Code ist so geschrieben, dass er (hoffentlich) leicht zu verstehen ist. Du brauchst eigentlich kein Array lang [len + 1, len + 1], Du brauchst nur lang [2, len + 1]. Sie können es indizieren wie nums [(i + 1)% 2, j]. –

1

Wenn eine Sequenz N Nullen mit den Indizes zero[0], zero[1], ... zero[N - 1] hat, wäre die Anzahl der Inversionen dafür (zero[0] + zero[1] + ... + zero[N - 1]) - (N - 1) * N/2. (Sie sollten in der Lage sein, dies zu beweisen)

Zum Beispiel hat 11010 zwei Nullen mit Indizes 2 und 4, so dass die Anzahl der Inversionen 2 + 4 - 1 * 2/2 = 5 wäre.

Für alle 2^k Sequenzen können Sie die Summe zweier Teile separat berechnen und addieren.

1) Der erste Teil ist zero[0] + zero[1] + ... + zero[N - 1]. Jede 0 in der der angegebenen Reihenfolge trägt und jeder index * 2^k? trägt index * 2^(k-1)

2) Der zweite Teil (N - 1) * N/2 ist. Sie können dies mit einer dynamischen Programmierung berechnen (vielleicht sollten Sie googlen und lernen Sie dies zuerst). Kurz gesagt, verwenden Sie f[i][j], um die Nummer der Sequenz mit j Nullen unter Verwendung der ersten i Zeichen der angegebenen Sequenz zu präsentieren.