C, 2765 (optymalna)
Edytować
Teraz wszystko w jednym pliku C. To po prostu znajduje wszystkie optymalne rozwiązania. Wszystkie muszą mieć 6 słów po 15 liter i jedno 10-literowe słowo składające się z 8 liter o wartości 1 i dwóch odstępów. W tym celu muszę załadować tylko ułamek słownika i nie muszę szukać 15-literowych słów ze spacjami. Kod jest prostym, wyczerpującym wyszukiwaniem w pierwszej kolejności.
#include <stdio.h>
#include <stdint.h>
#include <string.h>
struct w {
struct lc { uint64_t hi,lo; } lc;
char w[16];
} w15[6000], w10[40000];
int n15,n10;
struct lc pool = { 0x12122464612, 0x8624119232c4229 };
int pts[27] = {0,1,3,3,2,1,4,2,4,1,8,5,1,3,1,1,3,10,1,1,1,1,4,4,8,4,10};
int f[27],fs[26], w15c[27],w15l[27][6000];
int count(struct lc a, int l) { return (l < 16 ? a.lo << 4 : a.hi) >> 4*(l&15) & 15; }
int matches_val(uint64_t a, uint64_t b) {
uint64_t mask = 0x1111111111111111ll;
return !((a - b ^ a ^ b) & mask);
}
int matches(struct lc all, struct lc a) { return matches_val(all.hi,a.hi) && matches_val(all.lo,a.lo); }
int picks[10];
void try(struct lc cur, int used, int level) {
int c, i, must;
if (level == 6) {
for (i = 0; i<27; i++) if (count(cur, i) && pts[i]>1) return;
for (i = 0; i < n10; i++) if(!(used & (1 << (w10[i].w[0] & 31))) && matches(w10[i].lc, cur)) {
for (c = 0; c<level; c++) printf("%s ",w15[picks[c]].w);
printf("%s\n",w10[i].w);
}
return;
}
for (i = 0; i < 26;i++) if (count(cur,fs[i])) break;
must = fs[i];
for (c = 0; c < w15c[must]; c++) { i = w15l[must][c]; if(!(used & (1 << (w15[i].w[0] & 31))) && matches(cur, w15[i].lc)) {
struct lc b = { cur.hi - w15[i].lc.hi, cur.lo - w15[i].lc.lo };
picks[level] = i;
try(b, used + (1 << (w15[i].w[0] & 31)), level+1);
}}
}
int cmpfs(int *a, int *b){return f[*a]-f[*b];}
void ins(struct w*w, char *s, int c) {
int i;
strcpy(w->w,s);
for (;*s;s++)
if (*s&16) w->lc.hi += 1ll << 4*(*s&15); else w->lc.lo += 1ll << 4*(*s&15) - 4;
if (c) for (i = 0; i < 27;i++) if (count(w->lc,i)) f[i]++, w15l[i][w15c[i]++] = w-w15;
}
int main() {
int i;
char s[20];
while(scanf("%s ",s)>0) {
if (strlen(s) == 15) ins(w15 + n15++,s,1);
if (strlen(s) == 10) ins(w10 + n10++,s,0);
}
for (i = 0; i < 26;i++) fs[i] = i+1;
qsort(fs, 26, sizeof(int), cmpfs);
try(pool, 0, 0);
}
Stosowanie:
$time ./scrab <sowpods.txt
cc -O3 scrab.c -o scrab
JUXTAPOSITIONAL DEMISEMIQUAVERS ACKNOWLEDGEABLY WEATHERPROOFING CONVEYORIZATION FEATHERBEDDINGS LAURUSTINE
JUXTAPOSITIONAL DEMISEMIQUAVERS ACKNOWLEDGEABLY WEATHERPROOFING CONVEYORIZATION FEATHERBEDDINGS LUXURIATED
JUXTAPOSITIONAL DEMISEMIQUAVERS ACKNOWLEDGEABLY WEATHERPROOFING CONVEYORIZATION FEATHERBEDDINGS LUXURIATES
JUXTAPOSITIONAL DEMISEMIQUAVERS ACKNOWLEDGEABLY WEATHERPROOFING CONVEYORIZATION FEATHERBEDDINGS ULTRAQUIET
JUXTAPOSITIONAL DEMISEMIQUAVERS ACKNOWLEDGEABLY WEATHERPROOFING CONVEYORIZATION FEATHERBEDDINGS UTRICULATE
JUXTAPOSITIONAL DEMISEMIQUAVERS WEATHERPROOFING ACKNOWLEDGEABLY CONVEYORIZATION FEATHERBEDDINGS LAURUSTINE
JUXTAPOSITIONAL DEMISEMIQUAVERS WEATHERPROOFING ACKNOWLEDGEABLY CONVEYORIZATION FEATHERBEDDINGS LUXURIATED
JUXTAPOSITIONAL DEMISEMIQUAVERS WEATHERPROOFING ACKNOWLEDGEABLY CONVEYORIZATION FEATHERBEDDINGS LUXURIATES
JUXTAPOSITIONAL DEMISEMIQUAVERS WEATHERPROOFING ACKNOWLEDGEABLY CONVEYORIZATION FEATHERBEDDINGS ULTRAQUIET
JUXTAPOSITIONAL DEMISEMIQUAVERS WEATHERPROOFING ACKNOWLEDGEABLY CONVEYORIZATION FEATHERBEDDINGS UTRICULATE
OVERADJUSTMENTS QUODLIBETARIANS ACKNOWLEDGEABLY WEATHERPROOFING EXEMPLIFICATIVE HYDROGENIZATION RUBIACEOUS
OVERADJUSTMENTS QUODLIBETARIANS WEATHERPROOFING ACKNOWLEDGEABLY EXEMPLIFICATIVE HYDROGENIZATION RUBIACEOUS
real 0m1.754s
user 0m1.753s
sys 0m0.000s
Uwaga: każde rozwiązanie jest drukowane dwukrotnie, ponieważ podczas dodawania 15-literowego słowa „W” tworzone są 2 zamówienia, ponieważ istnieją 2 „W” płytki.
Pierwsze rozwiązanie z podziałem punktowym:
JUXTAPOSITIONAL 465
DEMISEMIQUAVERS 480
ACKNOWLEDGEABLY 465
WEATHERPROOFING 405
CONVEYORIZATION 480
FEATHERBEDDINGS 390
LAURUSTINE (LAURU?TI?E) 80
no tiles left
Edycja: wyjaśnienie
Co umożliwia przeszukiwanie całej przestrzeni? Dodając nowe słowo, biorę pod uwagę tylko te słowa, które mają najrzadszą pozostałą literę. W każdym razie ta litera musi zawierać jakieś słowo (i 15-literowe słowo, ponieważ będzie to litera o wartości innej niż 1, choć tego nie sprawdzam). Zacznę więc od słów, J, Q, W, W, X, Z
które się liczą 50, 100, 100, 100, 200, 500
. Na niższych poziomach jestem bardziej odcięty, ponieważ niektóre słowa są eliminowane przez brak liter. Szerokość drzewa wyszukiwania na każdym poziomie:
0: 1
1: 49
2: 3046
3: 102560
4: 724040
5: 803959
6: 3469
Oczywiście dużą część odcięcia uzyskuje się, nie sprawdzając nieoptymalnych rozwiązań (puste 15-literowe słowa lub krótsze słowa). Na szczęście dzięki temu słownikowi można uzyskać rozwiązanie 2765 (ale było blisko, tylko 2 kombinacje 15-literowych słów dają rozsądne resztki). Z drugiej strony łatwo jest zmodyfikować kod, aby znaleźć kombinacje o niższej punktacji, w których nie wszystkie 10 pozostałych liter ma 1-krotną wartość, choć trudniej byłoby udowodnić, że byłoby to optymalne rozwiązanie.
Również kod pokazuje klasyczny przypadek przedwczesnej optymalizacji. Ta wersja matches
funkcji spowalnia kod tylko o 30%:
int matches(struct lc all, struct lc a) {
int i;
for (i = 1; i < 27; i++) if (count(a, i) > count(all, i)) return 0;
return 1;
}
Wymyśliłem nawet, jak sprawić, by porównanie magii równoległych bitów było jeszcze krótsze niż w moim oryginalnym kodzie (w tym przypadku nie można użyć najwyższego skrawka, ale to nie jest problem, ponieważ potrzebuję tylko 26 z 32 skórek):
int matches_val(uint64_t a, uint64_t b) {
uint64_t mask = 0x1111111111111111ll;
return !((a - b ^ a ^ b) & mask);
}
Ale daje zerową przewagę.
Edytować
Pisząc powyższe wyjaśnienie, zdałem sobie sprawę, że większość czasu spędzam na skanowaniu listy słów pod kątem tych, które zawierają określoną literę, która nie jest w matches
funkcji. Obliczanie list z góry dało 10-krotne przyspieszenie.