OI - rozwiązania

hauleth
Moderator
  • Rejestracja:około 17 lat
  • Ostatnio:13 dni
0

Z racji, że OI się już skończyło to można by się już podzielić swoimi rozwiązaniami :) (nikt już pocztą też raczej nie wyśle, chyba, że ma lewe układy z Panią Z Okienka i mu wbije wczorajszą datę :P Może ja się pochwalę jedynym, na które miałem pomysł (zrobione w 2 dni bo miałem kompa w serwisie [sic!] i na więcej nie starczyło czasu), a mianowicie Litery:

Kopiuj
/**
 * @file lit.cpp
 *
 *     @date   12-11-2011
 *     @author Łukasz Niemier
 */

#include <cstdio>
#include <list>
#include <vector>

#define ord(c) ((c) - 'A')

const unsigned int DICT_SIZE = 'Z' - 'A' + 1;
const unsigned int MAX_SIZE = 1000002;

char jas[MAX_SIZE];
char malgosia[MAX_SIZE];
unsigned int strLen, i;
unsigned long long int swaps;
std::list<int> Q[DICT_SIZE];
std::vector<int> positions;

void gen_positions() {
	for (i = 0; i < strLen; i++)
		Q[ord(malgosia[i])].push_back(i);
	for (i = 0; i < strLen; i++) {
		positions[i] = Q[ord(jas[i])].front();
		Q[ord(jas[i])].pop_front();
	}
}

int merge(std::vector<int>& arr, std::vector<int>& temp, int left, int mid,
		int right) {
	int i, j, k;

	i = left; //< left subarray index
	j = mid;  //< right subarray index
	k = left; //< merged subarray index
	while ((i <= mid - 1) && (j <= right)) {
		if (arr[i] <= arr[j]) {
			temp[k++] = arr[i++];
		} else {
			temp[k++] = arr[j++];
			swaps = swaps + (mid - i);
		}
	}
	while (i <= mid - 1)
		temp[k++] = arr[i++];

	while (j <= right)
		temp[k++] = arr[j++];

	for (i = left; i <= right; i++)
		arr[i] = temp[i];

	return swaps;
}

int merge_sort(std::vector<int>& arr, std::vector<int>& temp, int left,
		int right) {
	int mid;
	if (right > left) {
		mid = (right + left) / 2; //< divide array to two parts

		merge_sort(arr, temp, left, mid);
		merge_sort(arr, temp, mid + 1, right);

		merge(arr, temp, left, mid + 1, right);
	}
	return swaps;
}

int inv_count(std::vector<int>& arr) {
	std::vector<int> temp(arr.size());
	return merge_sort(arr, temp, 0, arr.size() - 1);
}

int main(int argc, char **argv) {
	scanf("%u", &strLen);
	scanf("%s", jas);
	scanf("%s", malgosia);
	positions.resize(strLen);

	gen_positions();
	inv_count(positions);

	printf("%llu", swaps);

	return 0;
}

Zrobione częściowo na wektorach bo z nimi było mniej babrania się.


hauleth
@Wibowit, bo zgodnie z treścią zadania akceptuje tylko duże litery alfabetu łacińskiego http://www.ideone.com/4pwSl#li_w6JVP.
piternet
  • Rejestracja:prawie 15 lat
  • Ostatnio:prawie 6 lat
  • Postów:162
0

Ja zrobiłem raczej wzorcówkę do Liter, bruta do Randki i bruta do Odległości. Myślę, że starczy żeby przejść. Mam jeszcze 3 lata na starty w OIu także w następnych latach będzie lepiej. Mój kod do Liter:

Kopiuj
//Autor: Piotr Nosek

#include <iostream>
#include <algorithm>
#include <queue>

using namespace std;

int n;
queue <int> literki[26];
int gdzie[1000009];
long long load[1000009];

void add(int x, int v = 0)
{
    while(x <= n)
    {
        load[x]+=v;
        x += x & (-x);
    }
}

int query(int x)
{
    int res = 0;
    while(x > 0)
    {
        res += load[x];
        x -= x & (-x);
    }
    return res;
}

int main()
{
    ios_base::sync_with_stdio(0);
    string jas, gosia;
    cin >> n;
    cin >> jas >> gosia;
    for(int i=0; i<n; i++)
        literki[static_cast<int>(jas[i] - 'A')].push(i+1);
    for(int i=1; i<=n; i++)
    {
        int pom = literki[static_cast<int>(gosia[i-1] - 'A')].front();
        literki[static_cast<int>(gosia[i-1] - 'A')].pop();
        gdzie[pom] = i;
    }
    unsigned long long wynik = 0;
    for(int i=1; i<=n; i++)
        add(gdzie[i]);
    for(int i=1; i<=n; i++)
    {
        long long tmp = query(gdzie[i]);
        add(gdzie[i], 1);
        wynik+=gdzie[i] - 1 - tmp;
    }
    cout << wynik;
    return 0;
}

Myślę, że to dużo prostsze rozwiązanie niż twoje. Jakby ktoś chciał mogę wrzucić moje bruty do Randki i Odległości, ale brut do Randki ma ponad 200 linii, także nie chce tu spamować forum.

@Edit
Zarys mojego rozwiązania:
Kod działa na drzewach potęgowych. Idea polega na tym, że łączę w 'pary' każdą literkę z drugiego stringa(Gosi) z pierwszym(Jasia). W ten sposób np. dla testu
3 ABC BCA
Numeruję sobie kolejne literki w pierwszym stringu od jedynki do n. Każdą literę w drugim stringu łączę z pierwszą woloną w pierwszym stringu (mam to na jakiejś kolejce). Powstają mi dwa ciągi:
123 231.
Teraz lecę po drugim stringu, ale w takiej kolejności jakbym je posortował, czyli najpierw 1 potem 2 potem 3. Czyli najpierw będę w elemecie nr 3, bo tam jest 1, potem w elemencie nr 1 bo tam jest 2 itd. No i teraz lecąc tak zliczam sobie ile już wcześniej 'odwiedziłem' liter na lewo, czyli ile jest od tego elementu mniejszych elementów na lewo. Jak odwiedzam dany element to sobie takie coś odnotowuję i wrzucam na drzewko potęgowe, a takie drzewo w czasie log n zwraca mi sumę elementów z danego przedziału.
Co do sum bitowych - szczerze to nie mam pojecia jak to działa, ale znajduje to największą potęgę dwójki dzielącą x. Jest to związane z zapisem w pamięci komputera liczb ujemnych, zasłyszałem o tym i używam.
@edit2
A to może walnę tu krótki opis rozwiązania brute-force do Odległości. Złożoność: O(n^2 + max), albo jakoś tak, gdzie max - największa liczba w ciągu (max. 1 000 000).
Moje rozwiązanie polegało na wygenerowaniu liczb pierwszych do maxa z liczb na wejściu i rozłożeniu każdej liczby na wejściu na czynniki pierwsze. Potem porównywałem ile co najmniej operacji trzeba wykonać by z jednej liczby zrobić drugą, czyli tak na prawdę sprawdzić jaka jest różnica ilości poszczególnych liczb pierwszych w rozkładzie na czynniki pierwsze. Potem szukałem minimalnej odległości. Kod:

Kopiuj
//Autor: Piotr Nosek
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int tab[1007];
int najw[1007];
int numTab[1000007];
vector<int> roz[1007];
int n, maxx;
vector <int> pierwsze;

int abs(int a) {return a > 0 ? a : -a;}

void sito()
{
    for(int i=2; i*i<=maxx; i++)
    {
        if(numTab[i])
            continue;
        for(int j=2*i; j<=maxx; j+=i)
            numTab[j] = 1;
    }
    for(int i=2; i<=maxx; i++)
        if(!numTab[i])
            pierwsze.push_back(i);
}

int odl(int a, int b)
{
    int wynik = 0;
    if(tab[a] == 1)
    {
        for(int i=2; i<=najw[tab[b]]; i++)
            wynik+=roz[b][i];
        return wynik;
    }
    if(tab[b] == 1)
    {
        for(int i=2; i<=najw[tab[a]]; i++)
            wynik+=roz[a][i];
        return wynik;
    }
    for(int i=2; i<=max(najw[tab[a]], najw[tab[b]]); i++)
    {
       wynik+=abs(roz[a][i] - roz[b][i]);
    }
    return wynik;
}

int main()
{
    ios_base::sync_with_stdio(0);
    cin >> n;
    for(int i=0; i<n; i++)
        cin >> tab[i];
    int *tmp = max_element(tab, tab+n);
    maxx = tab[tmp - tab];
    sito();
    for(int i=0; i<n; i++)
        for(int j=0; j<maxx; j++)
            roz[i].push_back(0);
    for(int i=0; i<n; i++)
    {
        int liczba = tab[i], j = 0;
        while(liczba > 1)
        {
            if(liczba % pierwsze[j] == 0)
            {
                roz[i][pierwsze[j]]++;
                liczba /=pierwsze[j];
                najw[tab[i]] = pierwsze[j];
            }
            else j++;
        }
    }
    for(int i=0; i<n; i++)
    {
        int maks = 1000000, ix = 0;
        for(int j=0; j<n; j++)
        {
            if(j == i)
                continue;
            int danaOdl = odl(i, j);
            if(danaOdl < maks)
            {
                maks = danaOdl;
                ix = j;
            }
        }
        cout << ix+1 << "\n";
    }
    return 0;
}
edytowany 2x, ostatnio: piternet
Zobacz pozostałe 4 komentarze
piternet
O(n log n). Operacja query wykonuje się w czasie log n.
piternet
możliwe że elementy które użyłem nie są zbyt szybkie, w sumie to nie znam się na tym tak dobrze. Aczkolwiek przyuważyłem pewien błąd u ciebie, masz funkcję 'int merge_sort', która zwraca zmienną typu ULL. To będzie dobrze działać? No i editnąłem raz jeszcze i dodałem opis i kod odległości.
hauleth
To bez różnicy bo i tak zmienna jest globalna i jest wypisywana z globu a nie wyjściowy parametr tej funkcji.
Wibowit
Prawdopodobieństwo, że liczba n jest pierwsza to ok 1/ lg n, a sprawdzenie przez dzielenie czy jest pierwsza zajmuje sqrt(n) dzieleń, a więc złożoność znajdywania wśród n pierwszych liczb naturalnych liczb pierwszych wyniosłaby Theta(n * sqrt(n) / lg(n)) :D Nie wiem jaka jest złożoność dla sita w sumie, ale może niewiele się różni.
Wibowit
Jak działa x & (-x)? Załóżmy, że x jest postaci abcd1000. -x = (~x + 1) = neg(abcd)0111 + 1 = neg(abcd)1000. x & (-x) równa się więc zatem abcd1000 & neg(abcd)1000 = 1000.
Wibowit
  • Rejestracja:prawie 20 lat
  • Ostatnio:około 15 godzin
0

Litery, działa niby dla dowolnych znaków, ale nie jest dobrze przetestowane.

Kopiuj
#include <stdio.h>

int d[2123456], h[256], n[1123456];
char a[1123456], b[1123456];

int main(int argc, char** argv) {
    int z, i = scanf("%d %s %s", &z, a, b);
    long long int t = 0;
    for (i = z - 1; i >= 0; i--) {
        d[i * 2] = d[i * 2 + 1] = 0;
        n[i] = h[a[i]];
        h[a[i]] = i;
    }
    for (i = 0; i < z; i++) {
        int j, r = 0, p = h[b[i]];
        h[b[i]] = n[p];
        t += p;
        for (j = 0; (z >> j); j++) {
            t -= (p % 2) * (d[r + p / 2] += 1 - p % 2);
            r += (z + (2 << j) - 1) >> (j + 1);
            p /= 2;
        }
    }
    printf("%lld\n", t);
    return 0;
}

To jest oczywiście przykład jak nie należy pisać kodu.

Edit:
Mam pewne intuicje dotyczącą zadania Odległość:

  • z warunku na metrykę wynika macierz odległości jest symetryczna oraz przekątna jest wypełniona zerami, więc odpada nam połowa obliczeń.
  • jeśli f(a) = d(a, 1) to:
    -- d(a, b) = f(a) + f(b) - 2 * f(gcd(a, b))
    -- f(a) można spamiętywać (nie tablicować na początku tylko przy okazji) w zwykłej tablicy,
  • dzielenia można zastąpić mnożeniem - http://agner.org/optimize/optimizing_assembly.pdf , rozdział: Repeated integer division by the same value (all processors), otrzymane stałe stablicować - a_i jest ograniczone przez milion, a więc obliczanie tych stałych powinno trwać ułamek sekundy,
  • należy skanować i wypełniać tablice w taki sposób, aby maksymalnie wykorzystać pamięć podręczną procesora,
  • jeśli na wejściu pojawiają się duplikaty, np (a_i1, a_i2, ... ) to dla a_ix wystarczy wybrać indeks dowolnego innego elementu z tego ciągu,

"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 5x, ostatnio: Wibowit
szypxx
Czekałem na twój post hehe.
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)