Z lekkim poślizgiem publikuję analizę kolejnego zadania z konkursu “Potyczki Algorytmiczne” 2011 - 1B “Wieże”. Zadanie nie było trudne, aczkolwiek dosyć ciekawe. Nie udało mi się uzyskać jakiegoś specjalnie optymalnego wyniku, aczkolwiek pomyślnie przeszedłem wszystkie testy i kolejne 10 punktów wylądowało na moim koncie. Warto czasem rozruszać komórki mózgowe, dlatego zapraszam do lektury.

Fotografia: Vittis from Lithuania, CC-BY;

Potyczki Algorytmiczne 2011: Zadanie “Wieże”.

Treść zadania jest nastepująca:
Po długich staraniach Bajtek zdołał rozmieścić n wież na szachownicy rozmiaru n×n, tak że żadne dwie wieże nie szachują się. Dla przypomnienia: wieża szachuje wszystkie pola szachownicy znajdujące się w tym samym wierszu lub w tej samej kolumnie co ona.

Niestety, chłopiec przypadkowo potrącił szachownicę, przez co niektóre z wież poprzewracały się i spadły. Czy pomógłbyś mu ustawić te wieże z powrotem? Bajtek prosi, żeby nie ruszać wież, które wciąż stoją na szachownicy.

Wejście:
W pierwszy wierszu standardowego wejścia znajduje się jedna liczba całkowita n (2 <= n <= 1 000), oznaczająca rozmiar szachownicy. Dalej następuje opis ustawienia wież na szachownicy: kolejne n wierszy zawiera po n znaków każdy. Znak ‘.’ oznacza puste pole, a litera ‘W’ reprezentuje pole zajmowane przez wieżę.

Możesz założyć, że na szachownicy stoi w wież, przy czym 1 <= w <= n − 1, oraz że żadna para stojących wież nie szachuje się.

Wyjście:
Twój program powinien wypisać na standardowe wyjście reprezentację odpowiednio zapełnionej szachownicy w postaci n wierszy zawierających po n znaków ‘.’ lub ‘W’ każdy. Na planszy powinno występować dokładnie n znaków ‘W’ reprezentujących wieże, przy czym w wież powinno stać dokładnie na takich samych pozycjach jak na wejściu. Żadne dwie wieże nie mogą się szachować. Jeśli istnieje więcej niż jeden sposób dostawienia n − w wież, Twój program może wypisać dowolny z nich.
Przykład:
Dla danych wejściowych:

8
........
.....W..
..W.....
.......W
W.......
........
.W......
........

jednym z poprawnych wyników jest:

....W...
.....W..
..W.....
.......W
W.......
......W.
.W......
...W....

Zobaczmy, jak można rozwiązać to zadanie.

Rozwiązanie.

Moje rozwiązanie polegało na zaalokowaniu wektora o rozmiarze n * (n + 2) dla szachownicy o rozmiarze n. Pierwsze n elementów zawierało informację o tym, czy w danym wierszu znajduje się wieża, kolejne w elementów odnosiło się do kolumn. Reszta pamięci była zarezerwowana na całą szachownicę. Dane były typu bool, a więc przechowywałem tylko informację o tym, czy na danym polu / kolumnie / wierszu stoi pionek.

Pętla rozwiązująca zadanie musiała analizować zawartość pierwszych dwóch n elementów i w momencie, kiedy znalazła “pusty” wiersz i kolumnę, stawiała tam pionek i oznaczała wiersz i kolumnę jako zajętą.

Oto kod, jaki wyprodukowałem:

#include <cstdlib>
#include <cstdio>

int main(void)
	{
	int size = 0, i, j;
	scanf("%d\n", &amp;size);
	bool *rows = (bool *)calloc(size * (size + 2), sizeof(bool));
	char current = 0;
	// read
	for(i = 0; i < size; i++)
		{
		for(j = 0; j < size; j++)
			{
			scanf("%c", &amp;current);
			if('\n' == current)
				{
				j--;
				continue;
				}
			if('W' == current)
				{
				rows[i] = true;
				rows[size + j] = true;
				rows[size * (i + 2) + j] = true;
				}
			}
		}
	// solve
	for(i = 0; i < size; i++)
		{
		for(j = 0; j < size; j++)
			{
			if(rows[size * (i + 2) + j] == true)
				{
				printf("W");
				}
			else if(rows[i] == false &amp;&amp; rows[size + j] == false)
				{
				rows[i] = true;
				rows[size + j] = true;
				// rows[size * (i + 2) + j] = true;
				printf("W");
				}
			else
				{
				printf(".");
				}
			}
		printf("\n");
		}
	return 0;
	}

Optymalizacje.

Analizując zadanie już po oddaniu zauważyłem, że prawdopodobnie o wiele szybszą metodą byłoby analizowanie pozycji wież tylko po ich współrzędnych, zamiast operować na całej szachownicy. Plansza mogła mieć maksymalnie 1000 pól, co w najgorszym przypadku daje nam milion [1000 * 1000] iteracji. Niestety nie miałem pomysłu, jak można to w miarę szubko zakodować, dlatego w momencie, kiedy mój kod zaczął wykonywać najgorszy przypadek w czasie około 0,7s stwierdziłem, że zostawię to tak, jak jest.

Uważam, że dosyć istotnym zyskiem jest “skompresowanie” procesu alokowania pamięci do jednego odwołania do calloc() - na początku miałem trzy alokacje dla każdego fragmentu tablicy wynikowej, co jednak zabierało cenne milisekundy.

Ciąg dalszy… stoi pod znakiem zapytania.

Ze względu na to, że konkurs odbywa się w wyjątkowo niesprzyjającym dla mnie okresie czasu [maj to już przedsesyjne zaliczenia, a czerwiec przynosi “upragnioną” sesję] nie wiem, czy uda mi się rozwiązać jeszcze jakieś zadanie. Prawie zdążyłem dzisiaj oddać Wspinaczkę, jednakże o godzinie 00:00 byłem “daleko w lesie”, także runda druga niestety zakończy się dla mnie wynikiem równym status quo. Pecha nawet nie ruszyłem.

Jutro popołudniu postaram się znaleźć chwilę wolnego, żeby spróbować z trzecią rundą, aczkolwiek jest już ona na dosyć wysokim poziomie [nie oszukujmy się, Wieże nie były wcale takie trudne ;]], także nie wiem, czy dam radę. Niektórzy z moich znajomych odpuścili sobie już na drugiej rundzie, ale może coś uda się naskrobać. ;]

Podsumowanie.

Tradycyjnie będę wdzięczny za wytknięcie wszelkiego rodzaju błędów w kodzie oraz komentarze dotyczące potencjalnych optymalizacji / dobrych praktyk, ktore można było w tym przypadku zastosować.

Jeśli udało Wam się rozwiązać któreś z zadań z pierwszej albo drugiej rundy - możecie śmiało zamieścić kod w komentarzu używając tagu {code lang=“cpp”}{/code} w nawiasach kwadratowych - tak, tak - trzeba zamienić napisane przeze mnie nawiasy klamrowe na kwadratowe, wtedy kod zostanie ładnie pokolorowany. W razie czego spokojnie - poprawię sam.