Kiel oni inversigas 32-bitan nombron?

Supozu oni havas 32-bitan nombron kaj volas inversigi ĝin bite, sendepende de la signo; 0x00000001 fariĝos 0x80000000, kaj 0x71643257, aŭ 01110001 01100100 00110010 01010111 en binara formo, fariĝos 11101010 01001100 00100110 10001110, aŭ 0xEA4C268E.

Kiel fari tion plej efike?

La simpla vojo estas ripeti en buklo tra la 32 bitoj kaj en ĉiu iteracio preni la lastan biton de unu flanko kaj konstrui novan nombron bito post bito:

unsigned reverseBits(unsigned n) {
    unsigned result = 0;
    for (int i = 0; i < 32; ++i) {
        // Extract the least significant bit of `n` and shift it to the correct position
        result = (result << 1) | (n & 1);
        // Shift `n` to process the next bit
        n >>= 1;
    }
    return result;
}

Kaj ĝi funkcias, sed ne estas efika, ĉefe pro la buklo mem, kiu enkondukas branĉojn. Ni povas iom ludi kun la buklo, anstataŭigante for kun do { … } while, sed tio ne multe helpos. Ĉu ekzistas vojo tute forigi la buklon?

Fakte, jes!

Observu kun respekto:
unsigned reverse(unsigned x) {
    	x = ((x & 0x55555555) << 1) | ((x >> 1) & 0x55555555);
    	x = ((x & 0x33333333) << 2) | ((x >> 2) & 0x33333333);
    	x = ((x & 0x0F0F0F0F) << 4) | ((x >> 4) & 0x0F0F0F0F);
    	x = (x << 24) | ((x & 0xFF00) << 8) |
            ((x >> 8) & 0xFF00) | (x >> 24);
    	return x;
}

Neniuj branĉoj, neniuj bukloj, nur ŝovoj kaj logikaj operacioj.

Ĉu ne estas genia? Kia domaĝo, ke ĝi ne estas mia kodo! 🙂 Mi ankaŭ solvintus la problemon per buklo.

Ludo kun moneroj

Ĝi estas logika enigmo ofte demandata ĉe laborintervjuoj.

Du ludantoj ludas ludon kun tablo formita kiel perfekta cirklo kun finia radiuso R kaj senlima aro de identaj moneroj en formo de disko kun radiuso r, ĉiuj kun la sama grandeco kaj pezo. La ludo disvolviĝas jene: la ludantoj metas monerojn sur la tablon laŭvice, unu post la alia.
Reguloj de la ludo:
  • Ĉiu monero devas kuŝi plata sur la tablo, ne sur aliaj moneroj.
  • La surfaco de la monero devas tuŝi la surfacon de la tablo.
  • La monero ne rajtas pendeti super la rando de la tablo.
  • Ĝi povas tuŝi antaŭe metitajn monerojn, sed ne povas puŝi ilin aŭ forigi ilin de la tablo.
La ludanto, kiu unue ne povas trovi lokon por sia monero, perdas.
Demando:
Ĉu ekzistas gajna strategio por unu el la ludantoj? Se jes, kia ĝi estas?

Analizo:
La finia radiuso de la tablo kaj la ne-nula radiuso de la moneroj, kune kun la postulo ke la monero kuŝas plate sur la tablo, garantias, ke la ludo estas finia. Ĉi tiu problemo estas konata kiel la problemo de „pakado de cirkloj en cirklo“, kaj estas ĝenerale konsiderata kiel NP-malfacila problemo. Tamen, kiam la radiusoj r kaj R havas grandan diferencon, la supra limo por la maksimuma nombro da moneroj povas esti aproksimita kiel N≈0,9×r2/R2.

400 moneroj kun radiuso r = 1 metitaj sur tablon kun radiuso R = 21.69 sen interkovro. Ne eblas aldoni pli da moneroj.
La ilustraĵo de packomania.com.

Estas retejo dediĉita nur al solvoj de tiaj problemoj.

Solvo de la enigmo

Jes, ekzistas gajna strategio: la ludanto, kiu faras la unuan movon, ĉiam gajnos. Li devas meti sian unuan moneron ĝuste en la centron de la tablo; ĉiu sekva monero li devas meti simetrie al la loko, kie la dua ludanto metis sian moneron, konsiderante la centron de la tablo kiel la centro de simetrio.

Se la dua ludanto sukcesas trovi lokon por sia monero, ĉi tiu strategio garantias, ke la unua ludanto ankaŭ trovos lokon por sia monero. Se ne estas sufiĉe da spaco, la dua ludanto renkontos la problemon unue.