Carte des îles (et d'une rivière)

20

introduction

Depuis plusieurs siècles, il y a une certaine rivière qui n'a jamais été cartographiée. La Guilde des Cartographes veut produire une carte de la rivière, cependant, ils n'ont jamais réussi à réussir - pour une raison quelconque, tous les cartographes qu'ils ont envoyés pour cartographier la rivière ont été mangés par des animaux sauvages de la région. Une approche différente est requise.

Description de l'entrée

La zone est une grille rectangulaire de cellules de longueur met de largeur n. La cellule en bas à gauche serait 0,0, et la cellule en haut à droite serait m-1,n-1. met nsont fournis dans l'entrée comme un tuple m,n.

En utilisant des techniques de sondage géographique à longue distance, l'emplacement des îles autour du fleuve a été identifié. La taille des îles (c'est-à-dire le nombre de cellules occupées par l'île) a également été identifiée, mais pas la forme. Nous fournissons ces informations dans un tuple s,x,ysest la taille de l'île et xet ysont les positions x et y d'une cellule particulière de cette île. Chaque tuple de l'entrée est séparé par des espaces, voici donc un exemple d'entrée:

7,7 2,0,0 2,3,1 2,6,1 2,4,3 2,2,4 8,0,6 1,2,6 3,4,6

Pour illustrer plus clairement, voici l'entrée sur un graphique:

 y 6|8 1 3
   5|
   4|  2
   3|    2
   2|
   1|   2  2
   0|2  
     =======
     0123456
     x

Description de la sortie

Générez une carte en utilisant des caractères ASCII pour représenter des parties de la zone. Chaque cellule sera soit #(terre) soit .(eau). La carte doit suivre ces règles:

  1. Définition. Une île est un groupe de cellules terrestres contiguës orthogonalement qui est entièrement délimité par des cellules fluviales et / ou la frontière de la zone.
  2. Définition. Une rivière est un groupe de cellules d'eau contiguës orthogonalement qui est entièrement délimité par des cellules terrestres et / ou la frontière de la zone, et ne contient pas de «lacs» (2x2 zones de cellules d'eau).
  3. Règle . La carte doit contenir exactement une rivière.
  4. Règle . Chaque cellule numérotée dans l'entrée doit faire partie d'un îlot contenant exactement des scellules.
  5. Règle . Chaque île de la carte doit contenir exactement une des cellules numérotées dans l'entrée.
  6. Règle . Il existe une seule carte unique pour chaque entrée.

Voici la sortie de l'exemple d'entrée:

#.#.##.
#....#.
#.##...
##..##.
###....
...##.#
##....#

Voici une autre entrée et sortie.

Contribution:

5,5 3,0,1 1,4,1 2,0,4 2,2,4 2,4,4

Production:

#.#.#
#.#.#
.....
###.#
.....
Absinthe
la source
3
Remarque: c'est la même question qu'un solveur Nurikabe .
absinthe
1
Pouvons-nous prendre des commentaires dans n'importe quel format pratique, ou devrions-nous nous en tenir à celui de la question?
Erik the Outgolfer le
1
c'est aussi le problème 4 de la compétition Dyalog 2012
ngn
@ngn Depuis quand "publier un hachage cryptographique" ... habituel? (mais je suppose que c'est autorisé lorsqu'un défi le permet explicitement)
user202729
1
voici un bookmarklet pour puzzle-nurikabe.com - il convertit le puzzle actuel en une entrée valide pour ce défi et l'affiche en rouge juste en dessous du tableau:javascript:(_=>{var t=Game.nurikabe().task,m=t.length,n=t[0].length,s=[m,n];for(var i=0;i<m;i++)for(var j=0;j<n;j++)if(t[i][j]>=0)s+=' '+[t[i][j],i,j];puzzleContainerDiv.insertAdjacentHTML('beforeend','<hr><tt style=color:red>'+s+'</tt><hr>')})();void(0)
ngn

Réponses:

10

C + PicoSAT , 2345 995 952 octets

#include<picosat.h>
#define f(i,a)for(i=a;i;i--)
#define g(a)picosat_add(E,a)
#define b calloc(z+1,sizeof z)
#define e(a,q)if(a)A[q]^A[p]?l[q]++||(j[++k]=q):s[q]||(i[q]=p,u(q));
z,F,v,k,n,h,p,q,r,C,*x,*A,*i,*l,*s,*j,*m;u(p){s[m[++n]=p]=1;e(p%F-1,p-1)e(p%F,p+1)e(p>F,p-F)e(p<=F*v-F,p+F)}t(){f(q,k)l[j[q]]=0;f(q,n)s[m[q]]=0;k=n=0;i[p]=-1;u(p);}main(){void*E=picosat_init();if(scanf("%d,%d",&F,&v)-2)abort();z=F*v;for(x=b;scanf("%d,%d,%d",&r,&p,&q)==3;g(p),g(0))x[p=F-p+q*F]=r;f(p,F*v-F)if(p%F)g(p),g(p+1),g(p+F),g(p+F+1),g(0);for(A=b,i=b,l=b,s=b,j=b,m=b;!C;){picosat_sat(E,C=h=-1);f(p,F*v)A[p]=picosat_deref(E,p)>0,i[p]=0;f(p,F*v)if(x[p])if(i[q=p]){for(g(-q);i[q]+1;)q=i[q],g(-q);g(C=0);}else if(t(),r=n-x[p]){f(q,r<0?k:n)g(r<0?j[q]:-m[q]);g(C=0);}f(p,F*v)if(!i[p])if(t(),A[p]){g(-++z);f(q,k)g(j[q]);g(C=0);f(q,n)g(-m[q]),g(z),g(0);}else{C&=h++;f(q,k)g(-j[q]);g(++z);g(++z);g(0);f(q,F*v)g(s[q]-z),g(q),g(0);}}f(p,F*v)putchar(A[p]?35:46),p%F-1||puts("");}

Essayez-le en ligne!

(Attention: ce lien TIO est une URL de 30 kilo-octets qui contient une copie minifiée de PicoSAT 965, donc vous ne pourrez peut-être pas le charger dans certains navigateurs, mais il se charge au moins dans Firefox et Chrome.)

Comment ça fonctionne

Nous initialisons le solveur SAT avec une variable pour chaque cellule (terre ou eau), et uniquement les contraintes suivantes:

  1. Chaque cellule numérotée est une terre.
  2. Chaque rectangle 2 × 2 a au moins un terrain.

Les autres contraintes sont difficiles à encoder directement dans SAT, nous exécutons donc le solveur pour obtenir un modèle, exécutons une séquence de recherches en profondeur d'abord pour trouver les régions connectées de ce modèle et ajoutons des contraintes supplémentaires comme suit:

  1. Pour chaque cellule numérotée d'une région terrestre trop grande, ajoutez une contrainte selon laquelle il devrait y avoir au moins une cellule d'eau parmi les cellules terrestres actuelles de cette région.
  2. Pour chaque cellule numérotée d'une région terrestre trop petite, ajoutez une contrainte selon laquelle il devrait y avoir au moins une cellule terrestre parmi les cellules d'eau actuelles bordant cette région.
  3. Pour chaque cellule numérotée dans la même région terrestre qu'une autre cellule numérotée, ajoutez une contrainte selon laquelle il devrait y avoir au moins une cellule d'eau le long du chemin des cellules terrestres actuelles entre elles (trouvée en parcourant les pointeurs parents laissés par la recherche en profondeur d'abord). ).
  4. Pour chaque région terrestre comprenant aucune cellule numérotée, ajoutez des contraintes qui
    • toutes ces cellules terrestres actuelles devraient être de l'eau, ou
    • au moins l'une des cellules d'eau actuelles bordant cette région devrait être terrestre.
  5. Pour chaque région d'eau, ajoutez des contraintes qui
    • toutes ces cellules d'eau actuelles devraient être des terres, ou
    • chaque cellule autre que ces cellules d'eau actuelles devrait être terrestre, ou
    • au moins une des cellules terrestres actuelles bordant cette région devrait être l'eau.

Profitant de l'interface incrémentielle de la bibliothèque PicoSAT, nous pouvons immédiatement réexécuter le solveur, y compris les contraintes ajoutées, en préservant toutes les inférences précédentes faites par le solveur. PicoSAT nous donne un nouveau modèle, et nous continuons à répéter les étapes ci-dessus jusqu'à ce que la solution soit valide.

C'est remarquablement efficace; il résout 15 × 15 et 20 × 20 instances en une infime fraction de seconde.

(Merci à Lopsy d' avoir suggéré cette idée de contraindre de manière interactive les régions connectées dans un solveur SAT incrémentiel, il y a quelque temps.)

Quelques cas de test plus grands de puzzle-nurikabe.com

Une page aléatoire de 15 × 15 puzzles durs ( 5057541 , 5122197 , 5383030 , 6275294 , 6646970 , 6944232 ):

15,15 1,5,1 3,9,1 5,4,2 1,6,2 2,11,2 2,2,3 3,9,3 2,4,4 1,10,4 5,12,4 3,1,5 1,3,5 3,8,5 1,13,5 5,5,6 1,12,6 1,2,8 2,9,8 1,1,9 2,6,9 6,11,9 3,13,9 5,2,10 2,4,10 4,10,10 1,5,11 2,12,11 2,3,12 2,8,12 5,10,12 1,5,13 1,9,13 1,6,14 1,8,14
15,15 4,2,0 2,5,0 1,3,1 2,14,2 1,3,3 2,11,3 1,13,3 1,5,4 11,7,4 1,9,4 1,4,5 1,8,5 2,10,5 12,14,5 3,5,6 1,4,7 2,10,7 3,9,8 4,0,9 1,4,9 1,6,9 3,10,9 1,5,10 1,7,10 8,9,10 1,1,11 10,3,11 2,11,11 6,0,12 1,11,13 2,9,14 1,12,14
15,15 2,2,0 8,10,0 2,3,1 2,14,2 2,3,3 3,5,3 3,9,3 2,11,3 5,13,3 6,0,4 3,7,4 3,3,5 2,11,5 2,6,6 1,8,6 1,4,7 2,10,7 1,6,8 2,8,8 5,3,9 2,11,9 2,7,10 7,14,10 2,1,11 4,3,11 2,5,11 1,9,11 2,11,11 2,0,12 4,6,13 1,11,13 3,4,14 1,12,14
15,15 2,0,0 2,4,0 3,6,1 2,10,1 1,13,1 2,5,2 2,12,2 3,0,3 2,2,3 4,7,3 2,9,3 1,14,3 1,4,4 1,8,4 2,12,5 4,2,6 3,4,6 1,14,6 7,7,7 1,10,8 2,12,8 3,2,9 2,14,9 2,0,10 2,6,10 1,10,10 2,5,11 4,7,11 2,12,11 1,14,11 3,2,12 3,9,12 1,1,13 2,4,13 3,8,13 2,10,14 5,14,14
15,15 1,3,0 1,14,0 3,7,1 3,10,1 2,13,1 3,1,2 4,5,2 2,12,3 3,3,4 1,8,4 1,1,5 3,5,5 1,9,5 5,13,5 3,3,6 1,8,6 2,2,7 2,12,7 1,6,8 1,8,8 2,11,8 2,1,9 4,5,9 2,9,9 2,13,9 2,6,10 4,11,10 1,2,11 3,9,12 2,13,12 3,1,13 2,4,13 3,7,13 1,0,14
15,15 2,8,0 2,4,1 2,7,1 1,10,1 6,4,3 1,1,4 12,5,4 3,11,4 5,13,4 3,10,5 3,0,6 1,6,6 2,8,6 4,13,7 2,3,8 1,6,8 3,8,8 2,14,8 2,4,9 5,1,10 4,3,10 1,9,10 6,13,10 3,8,11 1,10,11 3,4,13 2,7,13 3,10,13 1,6,14 1,14,14

Une page aléatoire de 20 × 20 puzzles normaux ( 536628 , 3757659 ):

20,20 1,0,0 3,2,0 2,6,0 1,13,0 3,9,1 3,15,1 2,7,2 3,13,2 3,0,3 2,3,3 3,18,3 3,5,4 2,9,4 2,11,4 2,16,4 1,0,5 2,7,5 1,10,5 1,19,5 3,2,6 1,11,6 2,17,6 2,0,7 3,4,7 5,6,7 2,9,7 4,13,7 3,15,7 1,3,8 1,10,8 1,14,9 2,18,9 3,1,10 2,4,10 1,8,10 1,10,10 3,12,10 3,16,10 1,9,11 1,17,11 2,19,11 2,0,12 2,2,12 1,4,12 4,6,12 2,13,12 2,15,12 1,14,13 2,17,13 1,3,14 2,5,14 4,7,14 2,15,14 3,0,15 1,2,15 2,13,15 3,18,15 3,7,16 7,10,16 1,17,16 2,0,17 2,3,17 2,5,17 3,11,17 3,15,17 1,0,19 1,2,19 1,4,19 2,6,19 5,8,19 1,11,19 1,13,19 3,15,19 2,18,19
20,20 1,0,0 1,4,0 5,8,0 1,17,0 1,19,0 2,17,2 3,6,3 2,10,3 2,12,3 4,14,3 6,0,4 3,4,4 4,7,4 1,11,4 1,18,4 1,6,5 3,12,5 4,15,5 4,4,6 2,16,6 2,19,6 6,0,7 3,10,7 2,12,8 2,17,8 3,3,9 2,5,9 4,8,9 2,10,9 3,0,10 1,2,10 5,14,10 2,16,10 2,19,10 7,7,11 3,12,12 2,17,12 2,2,13 4,4,13 3,6,13 4,14,13 3,0,14 1,3,14 1,5,14 3,16,14 1,2,15 1,9,15 2,11,15 5,13,15 3,19,15 1,4,16 3,6,16 1,3,17 1,12,17 1,14,17 1,16,17 6,0,19 2,2,19 3,5,19 2,7,19 5,9,19 1,11,19 2,13,19 1,15,19 4,17,19
Anders Kaseorg
la source
3

GLPK , (non concurrent) 2197 octets

Il s'agit d'une entrée non concurrente, comme

  • Je ne respecte pas le format des données d'entrée (car GLPK ne peut lire que les données d'entrée des fichiers) et
  • GLPK ne semble pas être disponible sur RIO.

Je vais enregistrer ici une version encore non golfée, mais fonctionnelle. Selon les commentaires, je pourrais essayer de le raccourcir + ajouter une explication s'il y a un intérêt. Jusqu'à présent, les noms de contraintes servent de documents sur place.

L'idée principale est de coder la contrainte des "îles contiguës" en introduisant une variable de flux préservée qui a une source prédéfinie à l'emplacement de l'indice. La variable de décision is_islandjoue alors le rôle de puits placables. En minimisant la somme totale de ce flux, les îles sont obligées de rester connectées dans l'optimum. Les autres contraintes mettent en œuvre plutôt directement les différentes règles. Ce qui me laisse perplexe dont j'ai toujours l'air d'avoir besoin island_fields_have_at_least_one_neighbor.

Cependant, les performances de cette formulation ne sont pas excellentes. En mangeant directement toute la complexité avec une grande quantité de contraintes, le solveur prend près de 15 secondes pour l'exemple 15 x 15.

Code (toujours non golfé)

# SETS
param M > 0, integer; # length
param N > 0, integer; # width
param P > 0, integer; # no. of islands

set X := 0..N-1;  # set of x coords
set Y := 0..M-1;  # set of y coords
set Z := 0..P-1;  # set of islands

set V := X cross Y;
set E within V cross V := setof{
  (sx, sy) in V, (tx, ty) in V :

  ((abs(sx - tx) = 1) and (sy = ty)) or 
  ((sx = tx) and (abs(sy - ty) = 1))
} 
  (sx, sy, tx, ty);


# PARAMETERS
param islands{x in X, y in Y, z in Z}, integer, default 0;
param island_area{z in Z} := sum{x in X, y in Y} islands[x, y, z];

# VARIABLES
var is_river{x in X, y in Y}, binary;
var is_island{x in X, y in Y, z in Z}, binary;
var flow{(sx, sy, tx, ty) in E, z in Z} >= 0;

# OBJECTIVE
minimize obj: sum{(sx, sy, tx, ty) in E, z in Z} flow[sx, sy, tx, ty, z];

# CONSTRAINTS
s.t. islands_are_connected{(x, y) in V, z in Z}:

    islands[x, y, z] 
  - is_island[x, y, z]
  + sum{(sx, sy, tx, ty) in E: x = tx and y = ty} flow[sx, sy, x, y, z]
  - sum{(sx, sy, tx, ty) in E: x = sx and y = sy} flow[x, y, tx, ty, z]
  = 0;


s.t. island_contains_hint_location{(x, y) in V, z in Z}:

    is_island[x, y, z] >= if islands[x, y, z] > 0 then 1 else 0;


s.t. each_square_is_river_or_island{(x, y) in V}:

    is_river[x, y] + sum{z in Z} is_island[x, y, z] = 1;


s.t. islands_match_hint_area{z in Z}:

    sum{(x, y) in V} is_island[x, y, z] = island_area[z];


s.t. river_has_no_lakes{(x,y) in V: x > 0 and y > 0}:

  3 >= is_river[x, y] + is_river[x - 1, y - 1]
     + is_river[x - 1, y] + is_river[x, y - 1];


s.t. river_squares_have_at_least_one_neighbor{(x, y) in V}:

    sum{(sx, sy, tx, ty) in E: x = tx and y = ty} is_river[sx, sy] 
 >= is_river[x, y];


s.t. island_fields_have_at_least_one_neighbor{(x, y) in V, z in Z: island_area[z] > 1}:

    sum{(sx, sy, tx, ty) in E: x = tx and y = ty} is_island[sx, sy, z]
 >= is_island[x, y, z];


s.t. islands_are_separated_by_water{(x, y) in V, z in Z}:

    sum{(sx, sy, tx, ty) in E, oz in Z: x = sx and y = sy and z != oz} is_island[tx, ty, oz]
 <= 4 * P * (1 - is_island[x, y, z]);

solve;

for{y in M-1..0 by -1}
{
    for {x in X} 
    {
        printf "%s", if is_river[x, y] = 1 then "." else "#";
    }
    printf "\n";
}

Données de problème

5 x 5

data;
param M := 5;
param N := 5;
param P := 5;
param islands :=
# x,y,z,area
  0,1,0,3
  4,1,1,1
  0,4,2,2
  2,4,3,2
  4,4,4,2;
end;

7 x 7

data;
param M := 7;
param N := 7;
param P := 8;
param islands :=
# x,y,z,area
  0,0,0,2 
  3,1,1,2 
  6,1,2,2 
  4,3,3,2 
  2,4,4,2 
  0,6,5,8 
  2,6,6,1 
  4,6,7,3;
end;

15 x 15

data;
param M := 15;
param N := 15;
param P := 34;
param islands :=
# x,y,   z,area
5,  1,   0, 1
9,  1,   1, 3
4,  2,   2, 5
6,  2,   3, 1
11, 2,   4, 2
2,  3,   5, 2
9,  3,   6, 3
4,  4,   7, 2
10, 4,   8, 1
12, 4,   9, 5
1,  5,  10, 3
3,  5,  11, 1
8,  5,  12, 3
13, 5,  13, 1
5,  6,  14, 5
12, 6,  15, 1
2,  8,  16, 1
9,  8,  17, 2
1,  9,  18, 1
6,  9,  19, 2
11, 9,  20, 6
13, 9,  21, 3
2,  10, 22, 5
4,  10, 23, 2
10, 10, 24, 4
5,  11, 25, 1
12, 11, 26, 2
3,  12, 27, 2
8,  12, 28, 2
10, 12, 29, 5
5,  13, 30, 1
9,  13, 31, 1
6,  14, 32, 1
8,  14  33, 1;
end;

Usage

Avoir glpsolinstallé, modéliser en un seul fichier (par exemple river.mod), saisir les données dans des fichiers séparés (par exemple 7x7.mod). Alors:

glpsol -m river.mod -d 7x7.mod

Cela, plus de patience, imprimera la solution dans le format de sortie spécifié (avec "quelques" sorties de diagnostic).

ojdo
la source
2
Je pense que cette réponse doit être considérée comme concurrente car il est possible pour d'autres personnes de vérifier qu'elle fonctionne. Les différences de format d'E / S devraient être couvertes par l'hypothèse que tout format d'E / S raisonnable devrait être accepté.
fəˈnɛtɪk
2
@ fəˈnɛtɪk D'accord. Il n'était pas éligible pour la prime de ngn qui vient de se terminer, ce qui nécessitait spécifiquement une réponse exécutable sur TIO, mais ce n'est pas une exigence de la question elle-même.
Anders Kaseorg
Étant donné que je n'ai pas commencé à jouer au golf, je ne le considérerai pas (encore) comme compétitif. Une fois que je suis sûr d'avoir élagué toutes les contraintes redondantes, je vais utiliser un caractère pour toutes les déclarations.
ojdo
3

Python 3 , 1295 octets

Il s'agit d'une solution python uniquement. Il n'utilise aucune bibliothèque et charge la carte via une entrée standard. Plus d'explications à venir. C'est ma première tentative sur un si grand golf. Il y a un lien vers le code commenté et non golfé en bas.

L,S,O,R,F=len,set,None,range,frozenset
U,N,J,D,I=S.update,F.union,F.isdisjoint,F.difference,F.intersection
def r(n,a,c):
 U(c,P)
 if L(I(N(Q[n],C[n]),a))<2:return 1
 w=D(P,N(a,[n]));e=S();u=S([next(iter(w))])
 while u:n=I(Q[u.pop()],w);U(u,D(n,e));U(e,n)
 return L(e)==L(w)
def T(a,o,i,c,k):
 s,p,m=a
 for _ in o:
  t=s,p,N(m,[_]);e=D(o,[_])
  if t[2] in c:o=e;continue
  c.add(t[2]);n=D(Q[_],m);U(k,n)
  if not J(i,n)or not r(_,N(m,i),k):o=e
  elif s==L(t[2]):yield t
  else:yield from T(t,N(e,n),i,c,k)
s,*p=input().split()
X,Y=eval(s)
A=[]
l=1,-1,0,0
P=F((x,y)for y in R(Y)for x in R(X))
exec("Q%sl,l[::-1]%s;C%s(1,1,-1,-1),l[:2]*2%s"%(('={(x,y):F((x+i,y+j)for i,j in zip(',')if X>x+i>-1<y+j<Y)for x,y in P}')*2))
for a in p:a,x,y=eval(a);k=x,y;A+=[(a,k,F([k]))]
A.sort(reverse=1)
k=F(a[1]for a in A)
p=[O]*L([a for a in A if a[0]!=1])
g,h=p[:],p[:]
i=0
while 1:
 if g[i]is O:h[i]=S();f=O;g[i]=T(A[i],Q[A[i][1]],D(N(k,*p[:i]),[A[i][1]]),S(),h[i])
 try:p[i]=g[i].send(f)[2]
 except:
  f=I(N(k,*p[:i]),h[i]);g[i]=p[i]=O;i-=1
  while J(p[i],f):g[i]=p[i]=O;i-=1
 else:
  i+=1
  if i==L(p):
   z=N(k,*p)
   if not any(J(z,F(zip([x,x+1]*2,[y,y,y+1,y+1])))for x in R(X-1)for y in R(Y-1)):break
   for c in h:U(c,z)
b=[X*['.']for i in R(Y)]
for x,y in z:b[y][x]='#'
for l in b[::-1]:print(''.join(l))

Essayez-le en ligne!

Regardez le code non golfé .

Le Matt
la source