BOI 2016
CEOI 2017
BOI 2018
BOI 2019
BOI 2020
CEOI 2020
Kolory
Linda lubi zmieniać kolor włosów (kolory numerujemy liczbami od 1 do )
i wie, że jej chłopak to zauważy, jeśli różnica bezwzględna między starym a nowym
kolorem wynosi co najmniej .
Linda chce wyznaczyć wartość , wykonując zmiany kolorów włosów i obserwując
reakcje chłopaka.
Nie można dwa razy użyć tego samego koloru.
Należy napisać program, który pozwoli jej to zrobić, używając jedynie 64 zapytań.
Mało kolorów
W podzadaniu 1 mamy , zatem możemy zapytać o wszystkie możliwe wartości ,
czyli kolejno o różnice .
Gdy dla którejś z różnicy odpowiedź chłopaka będzie negatywna, tzn. że mamy
.
W przeciwnym wypadku mamy .
Kłopotliwe jest jedynie wybieranie kolorów tak, aby żaden z nich się nie powtórzył.
Wystarczy jednak użyć kolorów w następującej kolejności:
W celu uproszczenia implementacji, będziemy zapamiętywać ostatnio zadane pytanie
.
Jeśli nowe pytanie jest o pozycję , to powiemy, że robimy skok .
Ponieważ kolejne skoki na liście mają naprzemienne znaki, więc również możemy zapamiętać
znak ostatniego skoku.
Funkcja jump pobiera jedynie długość skoku:
ll n, previous, dir;
int ask(ll p) {
printf("? %lld\n",p);
fflush(stdout);
int resp;
scanf("%d",&resp);
return resp;
}
void answer(int ans) {
printf("= %lld\n", ans);
exit(0);
}
int jump(ll jump) {
ll pos = previous + dir*jump;
previous = pos;
dir *= -1;
return ask(pos);
}
int main() {
scanf("%lld",&n);
dir = 1;
previous = 1;
ask(previous);
REP(i,n-1) {
if (!jump(n-1-i)) { answer(n-i); }
}
answer(1);
}
W drugim podzadaniu mamy .
Jeśli więc , to możemy najpierw zadać pytanie o
różnicę .
W przypadku odpowiedzi pozytywnej, uruchamiamy poprzedni algorytm zadający pytań o
różnice
W przypadku odpowiedzi negatywnej, zadajemy pytań o różnice
Średnia liczba kolorów
W podzadaniu 3 mamy , co może być sugestią, że można zadać jedynie
„piewiastkową” liczbę pytań.
Podzielimy nasze rozwiązanie na dwie fazy: najpierw oszacujemy wartość z dokładnością
do , zadając pytań.
Dzięki temu, będziemy wiedzieć, że należy do przedziału od do
.
W drugiej fazie, za pomocą dodatkowych pytań o różnice
Dużo kolorów
Jeśli chcemy znaleźć rozwiązanie dla , to musi ono zadać pytań.
Naturalnym więc pomysłem jest wyszukiwanie binarne.
Musimy jednak uważać, aby nie powtórzyć żadnego koloru.
Po pierwsze, tak jak w pierwszym rozwiązaniu, zrobimy założenie, że skoki mają
naprzemienne znaki i że zaczynamy skokiem w prawo.
Po drugie, podejdziemy do zadania rekurencyjnie.
Chcemy znaleźć strategię dla kolorów, czyli przedziału .
Załóżmy na początek, że jest parzyste.
Niech będzie pierwszym pytaniem, które zadajemy w strategii dla kolorów:
fM fN fN+M
|---+--------| => |--------+---|--------+---|
Strategię dla kolorów zaczniemy od pytania , po którym zrobimy
skok w prawo o długości (czyli do pytania ).
Jeśli odpowiedź na pytanie była pozytywna, to znaczy, że ograniczyliśmy się
do przedziału , przy czym następny skok musimy wykonać w lewo.
Zatem chcielibyśmy przełączyć się teraz na strategię dla kolorów, ale wykonywać
skoki w drugą stronę.
Taka strategia wymaga pierwszego pytania w punkcie .
Musimy więc zadbać o to, żeby pytanie , które zadaliśmy w przedziale
było kompatybilne z tą strategią, czyli .
Jeśli odpowiedź na pytanie była negatywna, to ogranicza nas to do przedziału
.
Okazuje się, że tutaj też możemy zastosować strategię dla kolorów, ale
każdy skok musimy zwiększyć o .
Zauważmy, że przy takich skokach żaden kolor w przedziale się nie powtórzy,
bo będziemy skakać na przemian pomiędzy lewą a prawą połową przedziału,
a pozycje pytań wzięte modulo będą odpowiadały dokładnie pozycjom ze strategii
dla kolorów
(jest też tak dla początkowej pozycji ).
Tak więc po dokładnie jednym skoku zmniejszyliśmy długość przedziału dwukrotnie.
Przypadek gdy jest nieparzyste bazuje na tym samym pomyśle,
ale jest ciut bardziej wymagający technicznie.
Teraz dwa kawałki długości będą w przedziale długości nachodzić na siebie
jednym polem:
fM fN fN+M-1
|---+--------| => |--------+--|--|-------+---|
Pierwsze pytanie zadamy w , ale pierwszy
skok będzie miał długość .
Jeśli odpowiedź na pytanie była pozytywna, to ograniczyliśmy się do przedziału
.
Tutaj zastosujemy strategię (z rozpoczynającym skokiem w drugą stronę) dla kolorów.
Kluczowa jest obserwacja, że jest równe lub , zatem
przedział pozycji, w którym będziemy wykonywali skoki, zmieści się w lewej części długości .
Jeśli odpowiedź była negatywna, to ograniczamy się do przedziału ,
więc zastosujemy tutaj strategię dla kolorów, przy czym każdy skok zwiększymy o .
Tu z kolei kluczowe jest, że wszystkie skoki będą dłuższe niż , zatem nigdy nie wykonamy
skoku na środkowe pole.
W implementacji tego rozwiązania możemy połączyć oba przypadki w jeden.
Funkcja first oblicza pozycję pierwszego pytania,
a funkcja run implementuje strategię (jej drugi parametr określa o jak dużo
mamy zwiększać wszystkie skoki):
ll first(ll n) {
if (n == 1) { return 1; }
ll m = (n+1) / 2;
return m + 1 - first(m);
}
ll run(ll n, ll incr) {
if (n == 1) { return 1; }
ll m = (n+1) / 2, ms = n-m;
if (jump(ms + incr)) {
return run(ms, incr);
} else {
return ms + run(m, incr + ms);
}
}
int main() {
scanf("%lld",&n);
dir = 1;
previous = first(n);
ask(previous);
ll ans = run(n, 0);
answer(ans);
}