J'ai besoin d'aide sur ce problème ACM ICPC. Mon idée actuelle est de modéliser cela comme un problème de chemin le plus court, qui est décrit sous l'énoncé du problème.
Problème
Il y a N = 1000
des conteneurs de déchets nucléaires situés le long d'une ligne numérique 1-D à des positions distinctes de -500,000 to 500,000
, sauf x=0
. Une personne est chargée de collecter toutes les poubelles. Chaque seconde où un conteneur à déchets n'est pas collecté, il émet 1 unité de rayonnement. La personne commence x = 0
et peut déplacer l' 1
unité à chaque seconde, et la collecte des déchets prend un temps négligeable. Nous voulons trouver la quantité minimale de rayonnement libérée lors de la collecte de tous les conteneurs.
Exemple d'entrée:
4
Conteneurs situés à [-12, -2, 3, 7]
.
Le meilleur ordre pour collecter ces conteneurs est [-2, 3, 7, -12]
, pour un minimum d'émission d' 50
unités. Explication: la personne passe -2
en 2 secondes et pendant ce temps 2 units
des radiations sont émises. Il se rend ensuite à 3
(distance:) 5
pour que le baril libère des 2 + 5 = 7
unités de rayonnement. Il prend 4
plus de secondes pour arriver x = 7
là où ce baril a émis des 2 + 5 + 4 = 11
unités. Il prend 19
quelques secondes pour arriver x = -12
là où ce baril a émis des 2 + 5 + 4 + 19 = 30
unités. 2 + 7 + 11 + 30 = 50
, qui est la réponse.
Remarques
Il existe une O(N!)
solution évidente . Cependant, j'ai exploré des méthodes gourmandes telles que passer à la plus proche ou passer au cluster le plus proche, mais celles-ci n'ont pas fonctionné.
J'ai réfléchi à ce problème pendant un bon moment et je l'ai modélisé comme un problème de recherche de graphique:
- Nous insérons
0
comme position de base (ce sera l'état initial) - Ensuite, nous trions les positions du plus petit au plus grand.
- Nous faisons ensuite un BFS / PFS, où le se
state
compose de- Deux entiers
l
etr
qui représentent une plage contiguë dans le tableau de position trié que nous avons déjà visité - Un entier
loc
qui nous indique si nous sommes sur l'extrémité gauche ou droite de la plage - Un entier
time
qui nous indique le temps écoulé - Un «coût» entier qui nous indique le coût total jusqu'à présent (basé sur les nœuds que nous avons visités)
- Deux entiers
- De chaque état, nous pouvons passer à [l - 1, r] et [l, r + 1], en ajustant les 3 autres entiers en conséquence
- L'état final est [0, N], vérifiant les deux positions finales.
Cependant, il semble que [L, R, loc]
ne définit pas uniquement un état, et nous devons stocker L, R, loc, and time
, tout en minimisant cost
à chacun de ces derniers. Cela conduit à un algorithme exponentiel, qui est encore beaucoup trop lent pour tout bien.
Quelqu'un peut-il m'aider à développer mon idée ou à la pousser dans la bonne direction?
Edit: Peut-être que cela peut être modélisé comme un problème d'optimisation de programmation dynamique? En y réfléchissant, il a les mêmes problèmes que la solution de recherche de graphiques - ce n'est pas parce que le courant cost
est faible que c'est la réponse optimale à ce sous-problème, car cela time
affecte également considérablement la réponse.
Greedy ne fonctionne pas: j'ai un algorithme de sélection gourmand qui estime le coût du déplacement vers un certain endroit (par exemple, si nous nous déplaçons à droite, nous doublons les distances vers les barils de gauche et autres).
Pourriez-vous faire une recherche prioritaire, avec une heuristique? L'heuristique pourrait combiner le coût du voyage en cours avec le temps écoulé.
la source
Réponses:
Je pense que vous pouvez améliorer cela en regardant le problème comme un graphique dirigé de paires de positions.
Pour cet exemple, j'utiliserai la ligne avec les valeurs -9, -6, -1, 3 et 5.
Parce qu'il est trop difficile de dessiner un graphique avec juste du texte, je vais représenter les paires sous forme de tableau. Nous pouvons penser que les cellules représentent l'état où tous les conteneurs entre ces deux positions ont été collectés. Chaque cellule a deux valeurs, l'une représentant le coût pour aller à gauche, l'autre représentant le coût pour aller à droite (jusqu'au conteneur suivant).
Les valeurs peuvent simplement être calculées comme la distance entre les deux points multipliée par le nombre de barils en dehors de ces deux points + 1 . Les cellules où les deux nombres ont le même signe représentent des cas où tous les barils du signe opposé ont été collectés. Ceux-ci sont calculés en utilisant uniquement le nombre de barils dans le sens opposé à zéro.
Dans le tableau, les valeurs de X signifient que vous ne pouvez pas aller dans cette direction (tous les barils dans cette direction ont été pris). Les lignes représentent la position actuelle du collecteur et les colonnes représentent l'emplacement du baril opposé suivant.
Les règles de déplacement entre les carrés peuvent être quelque peu déroutantes.
Pour les colonnes numérotées négativement, les règles sont les suivantes:
Pour les colonnes numérotées positives, les règles sont les suivantes:
Nous pouvons maintenant exécuter l'algorithme de Dijkstra pour calculer le meilleur chemin, en utilisant ces règles de mouvement pour parcourir le graphique. Nos positions de départ sont (-1, 3) et (3, -1) avec des coûts initiaux de 5 et 15, respectivement. Une fois que nous avons calculé les valeurs pour les deux positions finales (à gauche de (-9, -9) et à droite de (5, 5)), la plus petite des deux est notre réponse.
Le temps d'exécution de chacune des étapes est le suivant:
Les deux premières étapes sont dominées par la dernière, donc votre temps d'exécution global est O (N ^ 2 log N), ce qui devrait être un temps d'exécution suffisant pour le défi.
la source
Distance la plus courte
J'ai écrit une application Java hier pour résoudre le problème. Le problème est essentiellement un problème de distance la plus courte, comme SRJ l'a dit dans son commentaire. Le rayonnement montre simplement que vous pouvez accumuler une valeur avec la distance la plus courte.
En gros, voici ce que j'ai fait.
Voici quelques sorties de l'application
Je n'ai optimisé l'algorithme en aucune façon. Je n'ai même pas remarqué que la liste d'entrée des numéros était triée. (Je suis un doofus.)
Lorsque j'ai exécuté mon code avec les valeurs maximales, 1 000 conteneurs et une plage de -500 000 à 500 000, mon code a pris 3 secondes pour s'exécuter. La plupart de ce temps écrivait les 1 000 lignes d'impression sur la console.
Je ne suis pas un grand O, mais je pense que ma force brute parcourant l'algorithme de chemin le plus court est O (N au carré), pas O (N!).
Si j'ai profité du fait que les numéros d'entrée sont triés, de sorte que je n'ai eu qu'à vérifier les deux numéros de chaque côté de l'endroit où je me trouve actuellement, je pourrais faire descendre l'application près de O (N). Je n'ai qu'à vérifier 2 chiffres parce que je supprime les éléments de la liste au fur et à mesure que j'y arrive.
Vous avez utilisé différents algorithmes, comme l'algorithme gourmand, pour trouver une solution approximative.
Si mon programme avait duré 3 heures au lieu de 3 secondes, alors vous auriez le choix.
La bonne solution est-elle assez bonne?
En d'autres termes, suis-je prêt à échanger la vitesse de traitement contre une bonne réponse?
Si une bonne réponse est suffisante, vous utilisez des algorithmes d'approximation.
Si vous voulez la réponse parfaite, vous devez parcourir tous les chemins les plus courts. Il n'y a pas de raccourci.
Si quelqu'un veut que je poste mon code, je le ferai. Je suis sûr qu'il y a encore des bugs, car je voulais voir si je pouvais implémenter un algorithme de marche le plus court.
la source
J'ai une solution qui résoudra ce problème à
2^N
temps, ce qui est médiocre, mais je pense que c'est une façon utile de formuler le problème, alors j'ai pensé que je publierais.Plutôt que de modéliser le problème sous forme de graphique, je modéliserais qu'il s'agit d'un arbre de décision binaire (disons
T
). À chaque niveau, vous devez choisir entre aller à droite ou à gauche. Il est assez facile de calculer le «coût» de chaque bord. Soith(K)
la hauteur du nœud actuelK
, puis le coût de l'arêteleft_child(K) = h(K) x dist(K, left_child(K))
. Un calcul similaire suffit pour le coût du bord pour le bon enfant. Vous construisez cet arbre, gardez une trace du coût cumulé des arêtes tout en bas et signalez le chemin d'accès au nœud feuille avec le coût total le plus petit.Notez que le calcul des coûts fonctionne parce que la longueur de chaque bord
dist(K, left_child(K))
représente le temps pour aller au site suivant, tandis que la hauteur de la sous-arborescence est le nombre de sites restants (par exemple, émettant toujours un rayonnement).Maintenant, l'astuce dans ce cadre consiste à déterminer s'il existe des heuristiques que vous pouvez utiliser pour «prouver» que vous pouvez ignorer l'extension de la recherche le long d'une branche. Mon intuition est que pour une telle heuristique, il existe un arrangement de sites qui le vaincra, mais peut-être que quelqu'un peut trouver quelque chose.
Un certain nombre ont proposé d'appliquer des solutions de chemin le plus court pour les graphiques, j'ai quelques doutes quant à savoir si une telle solution peut fonctionner. Vos voisins dans le «graphique» du problème changeront en fonction du chemin que vous suivez. Par exemple dans votre message d'origine avec
[-12, -2, 3, 7]
si vous allez à-2
alors-12
et3
devenez «voisins» et si vous allez à3
alors-2
et7
êtes voisins. Chaque «paire» possible de valeurs positives et négatives peut potentiellement être des heures voisines dans le graphique final. Je ne connais aucun algorithme de chemin le plus court qui soit prouvablement correct dans un graphique dynamique.la source
Je pense qu'il est plus logique de penser à chaque étape simplement comme un choix binaire entre aller à droite vers le baril le plus proche et aller à gauche vers le baril le plus proche. Ayez simplement une fonction de coût qui détaille le nombre d'unités de rayonnement qui seraient encourues au total en effectuant un mouvement et choisissez celle qui a le coût le plus bas.
Ne considérez PAS simplement le baril le plus proche, mais supposez plutôt qu'en vous éloignant d'un baril, vous ajoutez effectivement deux fois le rayonnement, car en vous éloignant, vous avez également encouru le coût d'avoir à reculer dans cette direction plus tard.
Dans votre exemple de [-12, -2,3,7], un déplacement vers la gauche entraînerait un total de 14 (2 + 2 + 10) à gauche et 18 (2 + 2 + 5 + 9) à droite, pour un total de 22. Déplacer vers la droite entraînerait 10 (3 + 3 + 4) à droite et 26 (3 + 3 + 5 + 15) à droite. Clairement à gauche est la bonne solution au premier abord. Un calcul similaire peut être effectué pour chaque mouvement successif.
Après cela, le problème se réduit essentiellement à une recherche, donc la complexité devrait être O (nlog (n)), ce qui est BEAUCOUP mieux que O (n!). Je pense que c'est nécessairement la plus faible complexité qui peut exister pour ce problème car c'est fondamentalement un algorithme de recherche basé sur la comparaison pour lequel il n'est pas possible de faire mieux que O (nlog (n))
Apparemment, je n'étais pas assez clair avec cette description, j'ai donc décidé de la rendre un peu plus programmatique: 1. Calculer le coût engagé en allant à gauche et le coût engagé en allant à droite en fonction de la position actuelle 2. Déplacer la direction la moins chère 3. Retirez le baril atteint de la considération dans le calcul du coût du déplacement dans une direction
Calcul du coût: 1. Identifiez le baril le plus proche dans la direction donnée. Disons que $ dist est la distance de la position actuelle au baril le plus proche dans la direction donnée. 2. Le coût est initialisé en N * $ dist où N ne considère que les barils encore actifs. 3. À cela, ajoutez la distance que la nouvelle position indiquée par $ dist serait de chaque baril restant.
la source
[43, -18, -98, -82, 63]
[-10,-11, 10,20,30,40,50,60,70]
. La seule solution correcte est de collecter tous les négatifs puis de collecter les positifs. Pour une réponse de 455.Solution partielle - j'y reviendrai plus tard.
Supposons que la stratégie "par défaut" soit exécutée complètement à gauche ou à droite, selon ce qui est le moins cher. Maintenant demandez, vaut-il un petit détour dans l'autre sens pour ramasser un baril. Il est assez facile de calculer la réponse.
Pour votre échantillon d'entrée, courir à droite est moins cher qu'à gauche. Est-ce que aller à -2 vaut un détour? Cela réduit le coût de la course à droite puis de nouveau à 0 par 14 (parce que vous "payiez" 4 unités de rayonnement par coup de 0 à 3 sur la stratégie par défaut, maintenant c'est à 3, vous en payiez 3 sur 3 à 7, maintenant c'est 2, etc.), plus réduit de 1 par coup votre coût de déplacement de 0 à -2, économisant ainsi 2 de plus pour un total de 16.
Cependant, cela ajoute un coût pour aller à -2 puis revenir à 0 sur 14 (4 unités par coup à -2, 3 par coup à 0), pour un gain net de (16-14) = 2. Notez que pour calculer cela, vous n'avez pas à évaluer le coût exact de la résolution de tout le problème pour chaque décision - vous avez suffisamment d'informations pour décider simplement en sachant si courir à gauche est moins cher que courir à droite, plus comment de nombreux conteneurs de déchets sont de chaque côté de vous, et la distance au plus proche 2. C'est donc O (N ^ 2).
À l'exception d'un problème important - j'ai supposé que vous courrez jusqu'à la fin, et nous savons que vous pourriez doubler. Pour nettoyer cela, nous devons mettre à jour mon calcul. Pour l'échantillon d'entrée, j'ai supposé que vous économiseriez 14 en émettant 1 rayonnement total de moins par unité par seconde tout en passant de 0 à 7 et inversement. Mais, si vous doublez avant de courir à 7, les économies seront réduites.
C'est assez mauvais, car je ne sais pas calculer le prochain double-back sans essayer toutes les possibilités, ce qui nous ramène à O (2 ^ N).
Sauf - c'est 2 ^ N avec l'élagage. J'ai calculé que le "voyage parallèle" à -2 coûtait 14, mais j'en ai gagné 16, si je n'avais pas eu plus de voyages secondaires avant d'arriver au nombre le plus à droite. Si le nombre le plus à droite avait été 5, je saurais immédiatement que le voyage parallèle à -2 ne pouvait pas porter ses fruits. (Coût encore 14, avantage maximum 12). Je ne dois pas non plus envisager d'aller à -2 puis de faire un détour avant d'atteindre 6, car c'est toujours inférieur à aller directement à ce point en premier lieu.
la source
Je pense que vous pouvez le résoudre en utilisant une recherche en largeur, en ne conservant pas plus de 2 * N ^ 2 tuples de (booléen, int, int, int, chaîne) où les chaînes sont aussi longues que le chemin est compliqué.
Les tuples sont (booléens min ou max, position min parcourue, position max parcourue, rayonnement total émis, historique du trajet).
Je vois l'algorithme comme ceci:
La recherche et la suppression de tuples dominés amélioreront considérablement les performances. Il pourrait être utile d'ajouter un indicateur «a élevé» à chaque tuple et de laisser des tuples élevés dans la piscine.
Il y a aussi des décisions importantes à prendre pour décider comment stocker les tuples et les rechercher pour les dominations et les nouveaux éléments à reproduire.
la source