TO JEST KOMENTARZ DO ZADANIA 'PROJEKT 1′ (teraz zwanego Zadanie 5) – http://skalski.at/2013/01/27/pjwstk-asd-projekt-1-rozwiazanie/
W tym kodzie zmieniłem nazwy zmiennych na 'opisowe’ i dodałem dużo komentarzy przy if’ach i pętlach. Samo działanie/algorytm bez zmian.
Do zrozumienia algorytmu trzeba wiedzieć co to są grafy (tutaj zaimplementowane jako tablice i listy).
Co do dużej ilości klas:
ListElementTown, ListTowns, ListElementThief, ListThiefs – to moje własne implementacje 'list’, równie dobrze do testów możecie użyć normalnej C++’owej listy http://www.cplusplus.com/reference/vector/vector/ (ma ona więcej funkcjonalości i jest znacznie wolniejsza, więc walcząc o sekundy i tak warto pomyśleć o własnym 'list’ i 'map’ [dla 'optymalizacja hardcore’owa])
Opis ogólny założeń programu:
kandydat – obiekt ktory liczy koszt kampani A/B i zajmuje miasta [tworzy sztaby wyborcze]
miasto – wierzcholek grafu, zawierajacego informacje o sasiedztwie z innymi miastami
sztab wyborczy – przypisanie danego miasta do kandydata, przypisanego miasta nie moze przejac inny kandydat
prawybory – jezeli w miescie nie ma jeszcze 'sztabu wyborczego’, a w ktoryms z miast sasiadujacych jest, to w danym miescie nalezy przeprowadzic prawybory zgodnie z podanym algorytmem (wygrywa ten kandydat ktorego sztaby sa w najwiekszej ilosci miast sasiednich)
tura – jeden obrot petli w ktorym kazde miasto w ktorym jest sztab proboje zalozyc sztab (doprowadza do prawyborow wpisujac swojego kandydata na liste) w sasiednich miastach
KANDYDACI JAKO WIRUSY:
Program mozna latwiej zrozumiec, patrzac na problem jak na komorki organizmu (miasta) i wirusy (kandydatow):
Wirusy znalazly sie w kilku komorkach, kazda komorka zarazona w danej 'turze’ zaraza wszystkie z ktorymi ma kontakt [miasta sasiedzkie] w nastepnej turze
Tak wiec mozna podzielic komorki na 3 grupy:
– nie zarazone
– zarażone
– zarażające
1. Kazda komorka w chwili zarazenia staje sie zarazajaca [w nastepnej turze!]
2. Kazda komorka po turze w ktorej zarazala staje sie 'zarazona’ [sama juz wiecej nie zaraza, nie ma po co, bo wszystkich sasiadow juz zarazila, a sasiadow nie przybywa]
Dana nie zarazona komorka, moze byc w danej turze zarazana przez wiecej, niz jedna sasiednia komorke, wygrywa ten wirus ktory ma najwiecej sasiadujacyh zarazonych komorek [przy remisie porownujemy ID wirusa wedlug polecenia w wersji A i B].
W zalozeniu kazda komorka ma jakiegos sasiada, wiec algorytm mieli dane do czasu, az ilosc zarazonych komorek jest rowna liczbie wszystkich komorek.
UWAGA! Opisany powyżej algorytm to teoretycznie najwydajniejsza wersja kodu, a nie ta jaka jest przedstawiona w C++ [zarażone ciągle zarażają.., dużo rzeczy jest zapisywane do tymczasowych list mimo, że mogło by działać bez nich itd.].
Kod C++ był pisany na ostatnią chwilę, a przed publikacją przeszedł jeszcze obróbkę po której zgodność z oryginałem według 'plagiatora’ wynosiła poniżej 5% [poprzez dodanie mniej wydajnych pętli, 'get’ w listach, deklaracje zmiennych w złych miejscach itd.]
ROZWIĄZANIE KTÓRE OPUBLIKOWAŁEM W C++ JEST DALEKIE OD TEORETYCZNIE NAJSZYBSZEGO. W DODATKU NIE JEST WYDAJNE (CO ZMIENIĆ OPISZĘ NA KOŃCU POSTA). Nie można wprost powiedzieć jakie rozwiązanie będzie najlepsze, bo na każdy algorytm duży wpływ ma ilość miast, kandydatów i połączeń między miastami, a jakie są ilości w danych testowych to nie wiem 🙁
#include<stdio.h> class Miasto; class Kandydat; class ListElementTown { public: Miasto* miasto; ListElementTown* nastepnyElementListy; ListElementTown(Miasto* m) { this->miasto = m; this->nastepnyElementListy = NULL; } ListElementTown(Miasto* m, ListElementTown* nastepnyElementListy) { this->miasto = m; this->nastepnyElementListy = nastepnyElementListy; } }; class ListTowns { public: ListElementTown* pierwszyElementListy; int iloscElementow; ListTowns() { pierwszyElementListy = NULL; iloscElementow = 0; } Miasto* get(int elementIterator) { ListElementTown* tmp = pierwszyElementListy; for(int i = 0; i < elementIterator; i++) tmp = tmp->nastepnyElementListy; return tmp->miasto; } void add(Miasto* m) { if(this->pierwszyElementListy == NULL) this->pierwszyElementListy = new ListElementTown(m); else pierwszyElementListy = new ListElementTown(m, pierwszyElementListy); iloscElementow++; } bool contains(Miasto* m) { ListElementTown* tmp = pierwszyElementListy; while(tmp) { if(m == tmp->miasto) return true; tmp = tmp->nastepnyElementListy; } return false; } void clear() { pierwszyElementListy = NULL; iloscElementow = 0; } }; class ListElementThief { public: Kandydat* kandydat; ListElementThief* nastepnyElementListy; ListElementThief(Kandydat* t) { this->kandydat = t; this->nastepnyElementListy = NULL; } ListElementThief(Kandydat* t, ListElementThief* nastepnyElementListy) { this->kandydat = t; this->nastepnyElementListy = nastepnyElementListy; } }; class ListThiefs { public: ListElementThief* pierwszyElementListy; int iloscElementow; ListThiefs() { pierwszyElementListy = NULL; iloscElementow = 0; } Kandydat* get(int elementIterator) { ListElementThief* tmp = pierwszyElementListy; for(int i = 0; i < elementIterator; i++) tmp = tmp->nastepnyElementListy; return tmp->kandydat; } void add(Kandydat* t) { if(this->pierwszyElementListy == NULL) this->pierwszyElementListy = new ListElementThief(t); else { pierwszyElementListy = new ListElementThief(t, pierwszyElementListy); } iloscElementow++; } bool contains(Kandydat* t) { ListElementThief* tmp = pierwszyElementListy; while(tmp) { if(t == tmp->kandydat) return true; tmp = tmp->nastepnyElementListy; } return false; } void clear() { pierwszyElementListy = NULL; iloscElementow = 0; } }; class Miasto { public: int id; Kandydat* kandydatKtoryZalozylSztabWyborczyA; Kandydat* kandydatKtoryZalozylSztabWyborczyB; Kandydat* zwyciezcaA; Kandydat* zwyciezcaB; ListThiefs* kandydaciA; ListThiefs* kandydaciB; ListTowns* miastaSasiedzkie; Miasto(int i) { id = i; kandydatKtoryZalozylSztabWyborczyA = NULL; kandydatKtoryZalozylSztabWyborczyB = NULL; zwyciezcaA = NULL; zwyciezcaB = NULL; kandydaciA = new ListThiefs(); kandydaciB = new ListThiefs(); miastaSasiedzkie = new ListTowns(); } }; class Kandydat { public: int id; int tymczasowyLicznikIlosciSztabowKandydataWmiastachSasiedzkichA; int tymczasowyLicznikIlosciSztabowKandydataWmiastachSasiedzkichB; int kosztKampaniA; int kosztKampaniB; Kandydat(int indeks) { this->id = indeks; this->tymczasowyLicznikIlosciSztabowKandydataWmiastachSasiedzkichA = 0; this->tymczasowyLicznikIlosciSztabowKandydataWmiastachSasiedzkichB = 0; this->kosztKampaniA = 0; this->kosztKampaniB = 0; } }; int main() { int iloscDzialajacychSztabowWyborczychA = 0; int iloscDzialajacychSztabowWyborczychB = 0; int iloscMiast = 0; int iloscKandydatow = 0; // wczytujemy ilosc miast fscanf(stdin, "%d", &iloscMiast); // tworzymy tablice z miastami Miasto* miasta[iloscMiast]; for(int i = 0; i < iloscMiast; i++) { // uzupelniamy ja pustymi miastami miasta[i] = new Miasto(i); } int miasto_1; int miasto_2; // wczytujemy po kolei po 2 id miast ktore sa sasiadami fscanf(stdin, "%d", &miasto_1); fscanf(stdin, "%d", &miasto_2); // lista tych miast konczy sie dwoma id miast -1 while(miasto_1 != -1 && miasto_2 != -1) { // graf ma byc nie skierowany, czyli jak sasiad X wie o sasiedzie Y to Y tez musi wiedziec o X // dlatego dodajemy informacje o sasiedze do obu miast miasta[miasto_1]->miastaSasiedzkie->add(miasta[miasto_2]); miasta[miasto_2]->miastaSasiedzkie->add(miasta[miasto_1]); fscanf(stdin, "%d", &miasto_1); fscanf(stdin, "%d", &miasto_2); } // wczytujemy ilosc kandydatow fscanf(stdin, "%d", &iloscKandydatow); // tworzymy tablice z kandydatkami Kandydat* kandydaci[iloscKandydatow]; for(int i = 0; i < iloscKandydatow; i++) { // uzupelniamy ja kandydatami ktorzy znaja swoje id kandydaci[i] = new Kandydat(i); } // wczytujemy sztaby poczatkowe kandydatow int idSztabuStartowegoKandydata; for(int i = 0; i < iloscKandydatow; i++) { // zalozenie jest takie, ze kazdy kandydat ma wlasne miasto, // wiec nie trzeba sprawdzac czy aby 2 nie jest naraz w tym samym miescie startowym fscanf(stdin, "%d", &idSztabuStartowegoKandydata); // dla algorytmu z zasada A i zasada B przypisujemy, ze ten kandydat juz wygral w tym miescie wybory miasta[idSztabuStartowegoKandydata]->kandydatKtoryZalozylSztabWyborczyA = kandydaci[i]; // naliczany koszt kampani wyborczej kandydaci[i]->kosztKampaniA++; // dodajemy +1 do ilosci dzialajacych sztabow wyborczych iloscDzialajacychSztabowWyborczychA++; // tutaj to samo co wyzej tylko dla algorytmu 'B' miasta[idSztabuStartowegoKandydata]->kandydatKtoryZalozylSztabWyborczyB = kandydaci[i]; kandydaci[i]->kosztKampaniB++; iloscDzialajacychSztabowWyborczychB++; } /* teraz dochodzimy do samego mielenia danych: w celu szybszych obliczen zrobimy sobie liste pomocnicza 'miastaWktorychTrzebaPrzeprowadzicPrawybory' zalozenia: mamy milion wierzcholkow grafu [miast] kazdy wierzcholek ma polaczenie z tysiacami innych wierzcholkow [innymi miastami] mamy kilkuset kandydatow ktorzy maja ustalone miasta startowe */ ListTowns* miastaWktorychTrzebaPrzeprowadzicPrawybory = new ListTowns(); // [PĘTLA GŁÓWNA] do poki nie we wszystkich miastach sa sztaby wyborcze while(iloscDzialajacychSztabowWyborczychA != iloscMiast && iloscDzialajacychSztabowWyborczychB != iloscMiast) { // co obrot petli (ture) czyscimy liste pomocnicza miastaWktorychTrzebaPrzeprowadzicPrawybory->clear(); // [PĘTLA 1] for(int i = 0; i < iloscMiast; i++) { // dla kazdego miasta .. if(miasta[i]->kandydatKtoryZalozylSztabWyborczyA != NULL && miasta[i]->kandydatKtoryZalozylSztabWyborczyB != NULL) { // .. w ktorym jest sztab wyborczy [X kandydata] // [PĘTLA 1.1] for(int i2 = 0; i2 < miasta[i]->miastaSasiedzkie->iloscElementow; i2++) { // dla kazdego miasta sasiedniego .. if(miasta[i]->miastaSasiedzkie->get(i2)->kandydatKtoryZalozylSztabWyborczyA == NULL && miasta[i]->miastaSasiedzkie->get(i2)->kandydatKtoryZalozylSztabWyborczyB == NULL) { // .. w ktorym nie ma sztabu wyborczego // wszystko jest w 'ifach', bo wiele innych miast moze byc sasiadem w ktorym jest juz sztab wyborczy, // a lista kandydatow i miast w ktorych maja byc wybor ma byc bez powtorzen if(!(miasta[i]->miastaSasiedzkie->get(i2)->kandydaciA->contains(miasta[i]->kandydatKtoryZalozylSztabWyborczyA))) { // dodaj do listy kandydatow A o ile jeszcze na niej nie jest miasta[i]->miastaSasiedzkie->get(i2)->kandydaciA->add(miasta[i]->kandydatKtoryZalozylSztabWyborczyA); } if(!(miasta[i]->miastaSasiedzkie->get(i2)->kandydaciB->contains(miasta[i]->kandydatKtoryZalozylSztabWyborczyB))) { // dodaj do listy kandydatow B o ile jeszcze na niej nie jest miasta[i]->miastaSasiedzkie->get(i2)->kandydaciB->add(miasta[i]->kandydatKtoryZalozylSztabWyborczyB); } if(!(miastaWktorychTrzebaPrzeprowadzicPrawybory->contains(miasta[i]->miastaSasiedzkie->get(i2)))) { // dodaj miasto sasiedzkie do listy miast w ktorych trzeba przeprowadzic prawybory w tej turze miastaWktorychTrzebaPrzeprowadzicPrawybory->add(miasta[i]->miastaSasiedzkie->get(i2)); } } } } } // koniec 'for' ktory generowal liste miast w ktorych trzeba przeprowadzic prawybory i dodawal w tych miastach kandydatow ListElementTown* elementPoczatkowy = miastaWktorychTrzebaPrzeprowadzicPrawybory->pierwszyElementListy; int loopsCounter = miastaWktorychTrzebaPrzeprowadzicPrawybory->iloscElementow; // teraz trzeba przeprowadzic prawybory i ustalic ktory kandydat wygral w danym miescie // przy czym jego wygrana nie moze wplywac na obliczenia innych wygranych (na to moze wplywac dopiero w nastepnej 'turze' algorytmu) // dlatego wynik prawyborow zapisujemy do zmiennych 'zwyciezcaA' i 'zwyciezcaB' // [PĘTLA 2] for(int i = 0; i < loopsCounter; i++) { Miasto* aktualneMiasto = elementPoczatkowy->miasto; Kandydat* tmpKandydat = NULL; // z dziwnych powodow trzymamy ilosc 'sztabow wyborczych w miastach sasiednich' w kandydatach, // wiec trzeba to co obrot petli czyscic w kazdym kandydacie for(int x = 0; x < iloscKandydatow; x++) { kandydaci[x]->tymczasowyLicznikIlosciSztabowKandydataWmiastachSasiedzkichA = 0; kandydaci[x]->tymczasowyLicznikIlosciSztabowKandydataWmiastachSasiedzkichB = 0; } int loopsCounter_2 = aktualneMiasto->miastaSasiedzkie->iloscElementow; ListElementTown* petlaPoSasiadach = aktualneMiasto->miastaSasiedzkie->pierwszyElementListy; // teraz dodajemy w tych kandydatach liczbe przechowujaca w ilu miastach sasiedzkich [dla 'aktualneMiasto'] jest sztab danego kandydata for(int ii = 0; ii < loopsCounter_2; ii++) { Miasto* tmpMiasto = petlaPoSasiadach->miasto; if(tmpMiasto->kandydatKtoryZalozylSztabWyborczyA != NULL) { tmpMiasto->kandydatKtoryZalozylSztabWyborczyA->tymczasowyLicznikIlosciSztabowKandydataWmiastachSasiedzkichA++; tmpMiasto->kandydatKtoryZalozylSztabWyborczyB->tymczasowyLicznikIlosciSztabowKandydataWmiastachSasiedzkichB++; } petlaPoSasiadach = petlaPoSasiadach->nastepnyElementListy; } // teraz sprawdzamy ktory kandydat ma najwiecej sztabow wyborczych w sasiadujacych miastach Kandydat* zwyciezcaA = aktualneMiasto->kandydaciA->get(0); Kandydat* zwyciezcaB = aktualneMiasto->kandydaciB->get(0); // dla A loopsCounter_2 = aktualneMiasto->kandydaciA->iloscElementow; for(int i2 = 1; i2 < loopsCounter_2; i2++) { tmpKandydat = aktualneMiasto->kandydaciA->get(i2); if((zwyciezcaA->tymczasowyLicznikIlosciSztabowKandydataWmiastachSasiedzkichA > tmpKandydat->tymczasowyLicznikIlosciSztabowKandydataWmiastachSasiedzkichA) || (zwyciezcaA->tymczasowyLicznikIlosciSztabowKandydataWmiastachSasiedzkichA == tmpKandydat->tymczasowyLicznikIlosciSztabowKandydataWmiastachSasiedzkichA && zwyciezcaA->id < tmpKandydat->id)) zwyciezcaA = tmpKandydat; } aktualneMiasto->zwyciezcaA = zwyciezcaA; aktualneMiasto->kandydaciA->clear(); // dla B loopsCounter_2 = aktualneMiasto->kandydaciB->iloscElementow; for(int i2 = 1; i2 < loopsCounter_2; i2++) { tmpKandydat = aktualneMiasto->kandydaciB->get(i2); if((zwyciezcaB->tymczasowyLicznikIlosciSztabowKandydataWmiastachSasiedzkichB < tmpKandydat->tymczasowyLicznikIlosciSztabowKandydataWmiastachSasiedzkichB) || (zwyciezcaB->tymczasowyLicznikIlosciSztabowKandydataWmiastachSasiedzkichB == tmpKandydat->tymczasowyLicznikIlosciSztabowKandydataWmiastachSasiedzkichB && zwyciezcaB->id > tmpKandydat->id)) zwyciezcaB = tmpKandydat; } aktualneMiasto->zwyciezcaB = zwyciezcaB; aktualneMiasto->kandydaciB->clear(); elementPoczatkowy = elementPoczatkowy->nastepnyElementListy; } elementPoczatkowy = miastaWktorychTrzebaPrzeprowadzicPrawybory->pierwszyElementListy; loopsCounter = miastaWktorychTrzebaPrzeprowadzicPrawybory->iloscElementow; // znamy juz zwyciezcow, teraz trzeba ich zwycieztwa w prawyborach przepisac na zalozone sztaby, aby w kolejnej 'turze' mogli zakladac kolejne sztaby // [PĘTLA 3] for(int i = 0; i < loopsCounter; i++) { Miasto* tmpMiasto = elementPoczatkowy->miasto; tmpMiasto->kandydatKtoryZalozylSztabWyborczyA = tmpMiasto->zwyciezcaA; tmpMiasto->kandydatKtoryZalozylSztabWyborczyB = tmpMiasto->zwyciezcaB; tmpMiasto->kandydatKtoryZalozylSztabWyborczyA->kosztKampaniA++; tmpMiasto->kandydatKtoryZalozylSztabWyborczyB->kosztKampaniB++; iloscDzialajacychSztabowWyborczychA++; iloscDzialajacychSztabowWyborczychB++; elementPoczatkowy = elementPoczatkowy->nastepnyElementListy; } } for(int i = 0; i < iloscKandydatow; i++) { fprintf(stdout, "%d %d\n",kandydaci[i]->kosztKampaniA, kandydaci[i]->kosztKampaniB); } return 0; }
Optymalizacja:
– zmiana wczytywania danych na szybkie wczytywanie ( http://skalski.at/2014/11/04/asd-spox-spoj-szybkie-wczytywanie-intow-w-c/ )
– przepisanie kodu tak, żeby nigdy nie korzystał z funkcji 'get’ [super nie wydajna!!] w listach (ZAWSZE algorytm iteruje po elementach listy w pętli od elementu zero do końca, więc zamiast 'for’ trzeba dać 'while’ który się zatrzyma jak 'next’ będzie NULLem)
– jeśli już nie przepisywać kodu na 'while’ to dodać w listach 'currentElement’ i 'currentIndex’, tak, aby po prośbie o element o 'id’ wyższym o jeden nie jechać pętlą od elementu zerowego do tego o 'id’ podanych tylko przejść do 'next’
– w pętlach wiele razy występują np. 'miasta[i]’ czy jeszcze gorsze 'miasta[i]->miastaSasiedzkie->get(i2)’, należało by przypisać wartość zmiennej do jakiejś zmiennej tymczasowej Miasto* tmpMiasto i z niej korzystać zamiast w jednej pętli po 8 razy odwoływać się do 2-3 skoków po pamięci RAM
– WSZYSTKIE zmienne tymczasowe powinny być zadeklarowane na początku 'main’, a nie w jakimkolwiek 'for’ czy 'while’, może to zaoszczędzić miliony wrzuceń i usunięć zmiennych z stosu co może dać te brakujące milisekundy do zaliczenia
– w listach funkcja 'clear’ nie czyści listy, więc jest tutaj duży wyciek pamięci, bo 'pierwszyElement’ ustawia na NULL, ale ten element mógł wskazywać na listę miliona elementów które już z RAM nie są wywalane, jaki to ma wpływ na wydajność nie wiem [może mieć pozytywny! warto sprawdzić czy aktualna wersja bez czyszczenia nie jest szybsza]
Optymalizacja hardcore’owa:
– kod oznaczony jako PĘTLA 1 i PĘTLA 2 uprościć poprzez nie używanie listy 'kandydaciA’ (na podstawie której PĘTLA 2 oblicza kto wygrał), tylko użycie mapy ( http://www.cplusplus.com/reference/map/map/) która by przechowywala danych kandydatow bez powtorzen i ile razy wystapili w miastach sasiedzkich [ pseudokod(!): std::map<Kandydat* kandydat, int iloscWystapien> kandydaciA ]