EvbCFfp1XB

problem and my answer.




Score 60秒で実行した時の例。結構ばらつきます。seed1 で40台が出ることもあれば44から改善しない時もあります。

  1. 40.815795138332106
  2. 43.53807408847798
  3. 124.3846766524884
  4. 24.75026428309017
  5. 44.13945193661654
  6. 99.17847374135275
  7. 20.081364429410037
  8. 70.93028342586867
  9. 80.47902244156333
  10. 191.91519882649064

1



Approach

ランダムに2点を交換するSA。
スコアは1番時間がかかる Vehicle の時間なので、交換する2点でスコアが変化しない時は、代わりに全 Vehicle の時間の合計を使う。
参考文献にあったストリングモデルのようなモデルを使う。

ストリングモデルは、次のような感じで
0, Vehicle1 の頂点集合, 0, Vehicle2 の頂点集合, ... , 0, VehicleM の頂点集合, 0
0で区切るモデルです。

私が使ったモデルは、次のような感じで
Vehicle2 の頂点集合の後半, Vehicle1 のID, Vehicle1 の頂点集合,  VehicleM のID, VehicleM の頂点集合, ... , Vehicle2 のID, Vehicle2 の頂点集合の前半
各VehicleのIDで区切って、リング状です。


Code

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;

public class VehicleRoutingProblem {
static final XorShift rng = new XorShift(System.nanoTime());
static final Watch watch = new Watch();

private int numPoints;
private int numVehicles;
private int[] x;
private int[] y;
private int[] cap;
private double[] inverseSpeed;
private double[][] distancePointToPoint;
private double[] times;
private int[] nodeToPoint;
private int[] pointToNode;
private SAState sa = new SAState();
private double sumTimes;
private double maxTimes;

private int[] nodeToVehicle;

private String[] run(int n, int m, int depotX, int depotY, int[] x, int[] y, int[] cap, int[] speed) {
init(n, m, depotX, depotY, x, y, cap, speed);
solve();

double max = 0;
for (int v = 0; v < numVehicles; v++) {
double timeV = calculateTime2(v);
Utils.debug("v", v, "time", timeV);
max = Math.max(max, timeV);
}
Utils.debug("time", watch.getSecondString(), "score", max);

return makeResult();
}

private String[] makeResult() {
String[] res = new String[numVehicles];
for (int v = 0; v < numVehicles; v++) {

ArrayList<Integer> points = new ArrayList<>();
for (int node = (pointToNode[numPoints + v] + 1) % nodeToPoint.length;; node = next(node)) {
if (isStartNode(node)) {
break;
}
points.add(nodeToPoint[node]);
}

StringBuilder sb = new StringBuilder();
sb.append("" + points.size());
for (int i = 0; i < points.size(); i++) {
sb.append(" " + points.get(i));
}
res[v] = sb.toString();
}
return res;
}

private void solve() {
SA();
}

private double calculateTime2(int vehicle) {
double distance = 0;
int startNode = pointToNode[numPoints + vehicle];
for (int i = 0;; i++) {
int node = (startNode + i) % nodeToPoint.length;
int nextNode = next(node);
if (i > 0 && isStartNode(node)) {
break;
}
distance += calculatePartDistance(vehicle, node, nextNode);
}
return distance * inverseSpeed[vehicle];
}

private void SA() {
double second = Math.ceil(watch.getSecond());
int mask = (1 << 10) - 1;
sa.init();
for (;; sa.numIterations++) {
if ((sa.numIterations & mask) == 0) {
sa.updateTime();

if (sa.isTLE()) {

Utils.debug(sa.numIterations, String.format("%.2f%%", 100.0 * sa.countChange / sa.numIterations), String.format("%.2f%%", 100.0 * sa.countAccept / sa.countChange), String.format("%5f", maxTimes), String.format("%.6f", 1.0 / sa.inverseTemperature), String.format("%.6f", 1.0 / sa.lastAcceptTemperature));
break;
}

sa.updateTemperature();
sa.updateRange();

if (watch.getSecond() > second) {
second++;
Utils.debug(sa.numIterations, String.format("%.2f%%", 100.0 * sa.countChange / sa.numIterations), String.format("%.2f%%", 100.0 * sa.countAccept / sa.countChange), String.format("%5f", maxTimes), String.format("%.6f", 1.0 / sa.inverseTemperature), String.format("%.6f", 1.0 / sa.lastAcceptTemperature));
}
}

change();
}
}

private void change() {
swap();
}

private int[] used;
private double[] currentTimes;

private int node1 = 0;

private void swap() {
node1++;
if (node1 >= nodeToPoint.length) {
node1 = 0;
}
int node2 = (int) ((nodeToPoint.length - 1) * rng.nextDouble());
if (node2 >= node1) {
node2++;
}
if (isStartNode(node1) || isStartNode(node2)) {
int originalVehicle1 = nodeToVehicle[node1];
int originalVehicle2 = nodeToVehicle[node2];

swap(node1, node2);

int vehicle1 = searchVehicle(node1);
int vehicle2 = searchVehicle(node2);

double newSum = sumTimes;
double newMax = 0;

for (int v = 0; v < numVehicles; v++) {
if (v == originalVehicle1 || v == originalVehicle2 || v == vehicle1 || v == vehicle2) {
currentTimes[v] = times[v];
times[v] = calculateTime2(v);
newSum += times[v] - currentTimes[v];
}
newMax = Math.max(newMax, times[v]);
}

double deltaScore = newMax - maxTimes;
if (Math.abs(deltaScore) < 1e-6) {
deltaScore = newSum - sumTimes;
}

sa.countChange++;
if (deltaScore < 1e-9 || sa.acceptUsingTomerunsPruning((deltaScore))) {
sa.countAccept++;
if (!(deltaScore < 1e-9)) {
sa.lastAcceptTemperature = sa.inverseTemperature;
}
sumTimes = newSum;
maxTimes = newMax;
for (int v = 0; v < numVehicles; v++) {
if (v == originalVehicle1 || v == originalVehicle2 || v == vehicle1 || v == vehicle2) {
setNodeToVehicle(v);
}
}

} else {
swap(node1, node2);
for (int v = 0; v < numVehicles; v++) {
if (v == originalVehicle1 || v == originalVehicle2 || v == vehicle1 || v == vehicle2) {
times[v] = currentTimes[v];
}
}
}
} else {
int vehicle1 = nodeToVehicle[node1];
int vehicle2 = nodeToVehicle[node2];
int previousNode1 = previous(node1);
int nextNode1 = next(node1);

int previousNode2 = previous(node2);
int nextNode2 = next(node2);

double currentTime1 = (calculatePartDistance(vehicle1, previousNode1, node1, previousNode1) + calculatePartDistance(vehicle1, node1, node1, nextNode1)) * inverseSpeed[vehicle1];
double currentTime2 = (calculatePartDistance(vehicle2, previousNode2, node2, previousNode2) + calculatePartDistance(vehicle2, node2, node2, nextNode2)) * inverseSpeed[vehicle2];

double newTime1;
double newTime2;
if (node1 == previousNode2) {
newTime1 = (calculatePartDistance(vehicle1, previousNode1, node2, previousNode1) + calculatePartDistance(vehicle1, node1, node2, node1)) * inverseSpeed[vehicle1];
newTime2 = (calculatePartDistance(vehicle2, previousNode2, node1, node2) + calculatePartDistance(vehicle2, node2, node1, nextNode2)) * inverseSpeed[vehicle2];
} else if (node2 == previousNode1) {
newTime1 = (calculatePartDistance(vehicle1, previousNode1, node2, node1) + calculatePartDistance(vehicle1, node1, node2, nextNode1)) * inverseSpeed[vehicle1];
newTime2 = (calculatePartDistance(vehicle2, previousNode2, node1, previousNode2) + calculatePartDistance(vehicle2, node2, node1, node2)) * inverseSpeed[vehicle2];
} else {
newTime1 = (calculatePartDistance(vehicle1, previousNode1, node2, previousNode1) + calculatePartDistance(vehicle1, node1, node2, nextNode1)) * inverseSpeed[vehicle1];
newTime2 = (calculatePartDistance(vehicle2, previousNode2, node1, previousNode2) + calculatePartDistance(vehicle2, node2, node1, nextNode2)) * inverseSpeed[vehicle2];
}
double newSum = sumTimes + newTime1 - currentTime1 + newTime2 - currentTime2;
double newMax = 0;
if (Math.abs(times[vehicle1] - maxTimes) < 1e-6 || Math.abs(times[vehicle2] - maxTimes) < 1e-6) {
times[vehicle1] += newTime1 - currentTime1;
times[vehicle2] += newTime2 - currentTime2;
for (int v = 0; v < numVehicles; v++) {
newMax = Math.max(newMax, times[v]);
}
} else {
times[vehicle1] += newTime1 - currentTime1;
times[vehicle2] += newTime2 - currentTime2;
newMax = Math.max(maxTimes, Math.max(times[vehicle1], times[vehicle2]));
}
double deltaScore = newMax - maxTimes;
if (Math.abs(deltaScore) < 1e-6) {
deltaScore = newSum - sumTimes;
}

sa.countChange++;
if (deltaScore < 1e-9 || sa.acceptUsingTomerunsPruning((deltaScore))) {
sa.countAccept++;
if (!(deltaScore < 1e-9)) {
sa.lastAcceptTemperature = sa.inverseTemperature;
}

swap(node1, node2);

sumTimes = newSum;
maxTimes = newMax;
} else {
times[vehicle1] -= newTime1 - currentTime1;
times[vehicle2] -= newTime2 - currentTime2;
}
}
}

private void setNodeToVehicle(int vehicle) {
nodeToVehicle[pointToNode[numPoints + vehicle]] = vehicle;
for (int node = next(pointToNode[numPoints + vehicle]);; node = next(node)) {
if (isStartNode(node)) {
break;
}
nodeToVehicle[node] = vehicle;
}
}

private void swap(int node1, int node2) {
int swap = nodeToPoint[node1];
nodeToPoint[node1] = nodeToPoint[node2];
nodeToPoint[node2] = swap;
pointToNode[nodeToPoint[node1]] = node1;
pointToNode[nodeToPoint[node2]] = node2;
}

private boolean isStartNode(int node) {
return nodeToPoint[node] >= numPoints;
}

private double calculatePartDistance(int vehicle, int node, int nodeFrom, int nodeTo) {
if (isEmpty(node, vehicle)) {
return distancePointToPoint[nodeToPoint[nodeFrom]][numPoints + vehicle] + distancePointToPoint[numPoints + vehicle][nodeToPoint[nodeTo]];
} else {
return distancePointToPoint[nodeToPoint[nodeFrom]][nodeToPoint[nodeTo]];
}
}

private double calculatePartDistance(int vehicle, int nodeFrom, int nodeTo) {
return calculatePartDistance(vehicle, nodeFrom, nodeFrom, nodeTo);
}

private boolean isEmpty(int node, int vehicle) {
int startNode = pointToNode[numPoints + vehicle];
int diffNodes = node >= startNode ? (node - startNode) : (node + pointToNode.length - startNode);
assert diffNodes >= 0 : Utils.toString("num", diffNodes);
return diffNodes % cap[vehicle] == 0;
}

private int searchVehicle(int node) {
if (isStartNode(node)) {
assert nodeToPoint[node] - numPoints >= 0 && nodeToPoint[node] - numPoints < numVehicles;
return nodeToPoint[node] - numPoints;
}
int vehicleNode = -1;
int maxVehicleNode = -1;
for (int v = 0; v < numVehicles; v++) {
if (pointToNode[numPoints + v] <= node) {
vehicleNode = Math.max(vehicleNode, pointToNode[numPoints + v]);
}
maxVehicleNode = Math.max(maxVehicleNode, pointToNode[numPoints + v]);
}
if (vehicleNode == -1) {
return nodeToPoint[maxVehicleNode] - numPoints;
}
return nodeToPoint[vehicleNode] - numPoints;
}

private int next(int node) {
return (node + 1) % nodeToPoint.length;
}

private int previous(int node) {
return (node - 1 + nodeToPoint.length) % nodeToPoint.length;
}

private void init(int n, int m, int depotX, int depotY, int[] x, int[] y, int[] cap, int[] speed) {
numPoints = n;
numVehicles = m;
this.x = new int[numPoints + numVehicles];
for (int i = 0; i < numPoints; i++) {
this.x[i] = x[i];
}
for (int i = 0; i < numVehicles; i++) {
this.x[numPoints + i] = depotX;
}
this.y = new int[numPoints + numVehicles];
for (int i = 0; i < numPoints; i++) {
this.y[i] = y[i];
}
for (int i = 0; i < numVehicles; i++) {
this.y[numPoints + i] = depotY;
}

this.cap = cap;
inverseSpeed = new double[speed.length];
for (int i = 0; i < speed.length; i++) {
inverseSpeed[i] = 1.0 / speed[i];
}

distancePointToPoint = new double[numPoints + numVehicles][numPoints + numVehicles];
for (int i = 0; i < numPoints + numVehicles; i++) {
for (int j = 0; j < numPoints + numVehicles; j++) {
double dx = this.x[i] - this.x[j];
double dy = this.y[i] - this.y[j];
distancePointToPoint[i][j] = Math.sqrt(dx * dx + dy * dy);
}
}

nodeToPoint = new int[numPoints + numVehicles];
for (int i = 0; i < numPoints + numVehicles; i++) {
nodeToPoint[i] = i;
}
pointToNode = new int[numPoints + numVehicles];
for (int i = 0; i < numPoints + numVehicles; i++) {
pointToNode[nodeToPoint[i]] = i;
}

times = new double[numVehicles];
for (int v = 0; v < numVehicles; v++) {
times[v] = calculateTime2(v);
}

sumTimes = 0;
maxTimes = 0;
for (int v = 0; v < numVehicles; v++) {
sumTimes += times[v];
maxTimes = Math.max(maxTimes, times[v]);
}
used = new int[numPoints + numVehicles];
Arrays.fill(used, -1);

currentTimes = new double[numVehicles];

nodeToVehicle = new int[numPoints + numVehicles];
for (int v = 0; v < numVehicles; v++) {
setNodeToVehicle(v);
}

Utils.debug("N", n, "M", m);

}

public static void main(String[] args) {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))) {

String[] split = reader.readLine().trim().split(" ");
int N = Integer.parseInt(split[0]);
int M = Integer.parseInt(split[1]);
split = reader.readLine().trim().split(" ");
int depotX = Integer.parseInt(split[0]);
int depotY = Integer.parseInt(split[1]);

int[] x = new int[N];
int[] y = new int[N];

for (int i = 0; i < N; i++) {
split = reader.readLine().trim().split(" ");
x[i] = Integer.parseInt(split[0]);
y[i] = Integer.parseInt(split[1]);
}

int[] cap = new int[M];
int[] speed = new int[M];

for (int i = 0; i < M; i++) {
split = reader.readLine().trim().split(" ");
cap[i] = Integer.parseInt(split[0]);
speed[i] = Integer.parseInt(split[1]);
}

VehicleRoutingProblem vrp = new VehicleRoutingProblem();
String[] ret = vrp.run(N, M, depotX, depotY, x, y, cap, speed);
for (int i = 0; i < ret.length; ++i) {
System.out.println(ret[i]);
}
System.out.flush();
} catch (Exception e) {
e.printStackTrace();
}
}

}

class XorShift {
private int w = 88675123;
private int x = 123456789;
private int y = 362436069;
private int z = 521288629;

public XorShift(long l) {
x = (int) l;
}

public int nextInt() {
final int t = x ^ (x << 11);
x = y;
y = z;
z = w;
w = w ^ (w >>> 19) ^ (t ^ (t >>> 8));
return w;
}

public long nextLong() {
return ((long) nextInt() << 32) ^ (long) nextInt();
}

public double nextDouble() {
return (nextInt() >>> 1) * 4.6566128730773926E-10;
}

public int nextInt(int n) {
return (int) (n * nextDouble());
}

}

class SAState {

public static final boolean useTime = true;

public double startTime = 0;
public double endTime = 59.5;
public double time = startTime;

public double startTemperature = 0.1;
public double endTemperature = 0;
public double inverseTemperature = 1.0 / startTemperature;
public double lastAcceptTemperature = startTemperature;

public double startRange = 1 << 20;
public double endRange = 1 << 20;
public double range = startRange;

public int numIterations;
public int countChange;
public int countAccept;

public void init() {
numIterations = 0;
countChange = 0;
countAccept = 0;
lastAcceptTemperature = startTemperature;
startTime = useTime ? VehicleRoutingProblem.watch.getSecond() : numIterations;

updateTime();
updateTemperature();
updateRange();
}

public void updateTemperature() {
inverseTemperature = 1.0 / (endTemperature + (startTemperature - endTemperature) * Math.pow((endTime - time) / (endTime - startTime), 1.0));
}

public void updateRange() {
range = endRange + (startRange - endRange) * Math.pow((endTime - time) / (endTime - startTime), 1.0);
}

public void updateTime() {
time = useTime ? VehicleRoutingProblem.watch.getSecond() : numIterations;
}

public boolean isTLE() {
return time >= endTime;
}

public boolean accept(double newScore, double currentScore) {
assert newScore - currentScore > 0;
assert 1.0 / inverseTemperature >= 0;
return VehicleRoutingProblem.rng.nextDouble() < Math.exp(-(newScore - currentScore) * (inverseTemperature));
}

public boolean acceptUsingTomerunsPruning(double newScore, double currentScore) {
assert newScore - currentScore > 0;
assert 1.0 / inverseTemperature >= 0;
return (-newScore + currentScore) * (inverseTemperature) > -10 && VehicleRoutingProblem.rng.nextDouble() < Math.exp(-(newScore - currentScore) * (inverseTemperature));
}

public boolean acceptUsingTomerunsPruning(double deltaScore) {
assert deltaScore > 0;
assert 1.0 / inverseTemperature >= 0;
return (-deltaScore) * (inverseTemperature) > -10 && VehicleRoutingProblem.rng.nextDouble() < Math.exp(-(deltaScore) * (inverseTemperature));
}

public boolean acceptUsingTomerunsPruningAndEvb(double newScore, double currentScore) {
assert newScore - currentScore > 0;
assert 1.0 / inverseTemperature >= 0;
return (-newScore + currentScore) * (inverseTemperature) > -10 && (VehicleRoutingProblem.rng.nextInt() & 2147483647) < 1 << (int) (31.5 + (-newScore + currentScore) * (inverseTemperature));
}

}

class Watch {
private long start;

public Watch() {
init();
}

public double getSecond() {
return (System.nanoTime() - start) * 1e-9;
}

public void init() {
init(System.nanoTime());
}

private void init(long start) {
this.start = start;
}

public String getSecondString() {
return toString(getSecond());
}

public static final String toString(double second) {
if (second < 60) {
return String.format("%5.2fs", second);
} else if (second < 60 * 60) {
int minute = (int) (second / 60);
return String.format("%2dm%2ds", minute, (int) (second % 60));
} else {
int hour = (int) (second / (60 * 60));
int minute = (int) (second / 60);
return String.format("%2dh%2dm%2ds", hour, minute % (60), (int) (second % 60));
}
}

}

final class Utils {
private Utils() {
}

public static final void debug(Object... o) {
System.err.println(toString(o));
}

public static final String toString(Object... o) {
return Arrays.deepToString(o);
}

}





Approach Guided Local Search(GLS) 。 TCO2016 MM R3 で CatalinT さんが使ったものと本質的に同じもの。


Score

kosakkunさんの適当なSAGLS
1494.01053692588624485.4605733810348
2530.1168033455548517.7671599235256
3367.93392449788024360.1746149965883
4493.59914978630474486.95184401291823
5364.88013978229515364.41532260772044
6304.6700126557216301.3079655440071
7482.3144426341467469.5112459237517
8579.3091643733519572.0601824834589
9352.68717930411583345.82962544607824
10558.819510787507553.5887500736407




source code

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;

public class EuclideanTravellingSalesmanProblem {
private static final long START = System.nanoTime();
private static final long END = (long) (START + 9.8 * 1e9);
private int N;
private double[][] distance;

private int[] nodeToCity;
private double score;

private int[] bestNodeToCity;
private double bestScore;

public int[] getOrder(double[] x, double[] y) {
init(x, y);
GLS();
return nodeToCity;
}

private void init(double[] x, double[] y) {
this.N = x.length;

nodeToCity = new int[N];
bestNodeToCity = new int[N];
for (int i = 0; i < nodeToCity.length; i++) {
nodeToCity[i] = i;
}

distance = new double[N][N];
for (int i = 0; i < distance.length; i++) {
for (int j = 0; j < i; j++) {
double dx = x[i] - x[j];
double dy = y[i] - y[j];
distance[i][j] = Math.sqrt(dx * dx + dy * dy);
distance[j][i] = distance[i][j];
}
}

score = calculateScore();
bestScore = score;

}

private void GLS() {
double[][] penalty = new double[N][N];
int[] maxUtilNodes = new int[N];
boolean[] isTarget = new boolean[N];
Arrays.fill(isTarget, true);

for (int iteration = 0;; iteration++) {
if ((iteration & ((1 << 10) - 1)) == 0) {
if (System.nanoTime() >= END) {
score = bestScore;
System.arraycopy(bestNodeToCity, 0, nodeToCity, 0, N);
break;
}
}

for (int i = 0, node = 0; i < N; i++, node = (node + 1) % N) {
if (!isTarget[nodeToCity[node]]) {
continue;
}

int B = node;
int A = B - 1;
if (A < 0) {
A = N - 1;
}

boolean update = false;
for (int node2 = 0; node2 < N; node2++) {
if (node2 == node) {
continue;
}
int C = node2;
int D = node2 + 1;
if (D == N) {
D = 0;
}

if (C == A) {
continue;
}
if (D == B) {
continue;
}

double A_BC_D = distance[nodeToCity[A]][nodeToCity[B]] + distance[nodeToCity[C]][nodeToCity[D]];
double A_CB_D = distance[nodeToCity[A]][nodeToCity[C]] + distance[nodeToCity[B]][nodeToCity[D]];
double deltaScore = A_CB_D - A_BC_D;

double pA_BC_D = penalty[nodeToCity[A]][nodeToCity[B]] + penalty[nodeToCity[C]][nodeToCity[D]];
double pA_CB_D = penalty[nodeToCity[A]][nodeToCity[C]] + penalty[nodeToCity[B]][nodeToCity[D]];
double deltaPenalty = (0.3 * bestScore / N) * (pA_CB_D - pA_BC_D);

if (deltaScore + deltaPenalty < 0) {
update = true;
score += deltaScore;

if (B <= C) {
for (int l = B, r = C; l < r; l++, r--) {
int swap = nodeToCity[l];
nodeToCity[l] = nodeToCity[r];
nodeToCity[r] = swap;
}
} else {
for (int l = C + 1, r = B - 1; l < r; l++, r--) {
int swap = nodeToCity[r];
nodeToCity[r] = nodeToCity[l];
nodeToCity[l] = swap;
}
}

if (score < bestScore) {
bestScore = score;
System.arraycopy(nodeToCity, 0, bestNodeToCity, 0, N);
}

break;
}

}

if (update) {
i = -1;
} else {
isTarget[nodeToCity[node]] = false;
}
}

double maxUtil = -1e9;
int size = 0;
for (int node = 0; node < N; node++) {
double util = distance[nodeToCity[node]][nodeToCity[(node + 1) % N]] / (penalty[nodeToCity[node]][nodeToCity[(node + 1) % N]] + 1.0);
if (util > maxUtil) {
maxUtil = util;
size = 0;
maxUtilNodes[size++] = node;
} else if (Math.abs(util - maxUtil) < 1e-5) {
maxUtilNodes[size++] = node;
}
}
for (int i = 0; i < size; i++) {
int node = maxUtilNodes[i];
int city1 = nodeToCity[node];
int city2 = nodeToCity[(node + 1) % N];
penalty[city1][city2]++;
penalty[city2][city1]++;
isTarget[city1] = true;
isTarget[city2] = true;
}

}
}

private double calculateScore() {
double score = 0.0;
for (int i = 0; i < N; i++) {
score += distance[nodeToCity[i]][nodeToCity[(i + 1) % N]];
}
return score;
}

public static void main(String[] args) {
EuclideanTravellingSalesmanProblem o = new EuclideanTravellingSalesmanProblem();

try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))) {

int N = Integer.parseInt(reader.readLine().trim());
double[] x = new double[N];
double[] y = new double[N];
for (int i = 0; i < N; i++) {
String[] split = reader.readLine().trim().split(" ");
x[i] = Double.parseDouble(split[0]);
y[i] = Double.parseDouble(split[1]);
}

int[] ret = o.getOrder(x, y);

StringBuilder sb = new StringBuilder();
for (int i = 0; i < N; i++)
sb.append(ret[i] + "\n");

System.out.println(sb.toString());
System.out.flush();

} catch (IOException e) {
e.printStackTrace();
}
}
}




yowaさんのスコアだと、0.905位。上位から4%ほど悪いらしい。

Approach
  • コインが200以下で始まった時、即終了。(1回くらいnotePlayしても良かったかも?)
  • コインが最初の25%以下になったら終了。(kelly criterion より多く賭けているとき終了というのを入れていた。そして、終了の時のコインの枚数を見ると6枚とかで、kelly criterionより全然少なく賭けていることがわかった。そしたら、最初のコインの枚数から大きく減るはずがないので、これを入れておいた。)
  • notePlay する確率 1から0に線形に減らす。(乱数使いたくなかった。)
    • notePlay することにしても、期待値+ボーナスが最も良いマシーンが40回以上 notePlay してたら、 quickPlay に変える。
    • quickPlay することにしても、期待値が1以下なら notePlay に変える。
  • リールは重複除去して覚える。(Testerで調べると、長さが30のリールでも、重複除去してもだいたい28〜30パターン残ったので、これで充分だと思った。)
  • ボーナス = 2 * Math.pow((40 - 選んだマシーンの現在のnotePlayの回数) / 40, 2) (UCBから変えた。)

強化学習したかった。



source code

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;

public class BrokenSlotMachines {
private XorShift rng = new XorShift(System.nanoTime());

private int coins;
private int maxTime;
private int noteTime;
private int numMachines;

private int startCoins;

private ArrayList<Character>[][] wheels;
private HashSet<String>[][] wheelPatternSet;

private int[] countNote;
private double[] expected;

public int playSlots(int coins, int maxTime, int noteTime, int numMachines) {
init(coins, maxTime, noteTime, numMachines);

solve();

Utils.debug("coins", this.coins);
return 0;
}

private void solve() {
for (int time = 0; time < maxTime;) {
if (coins <= 0.25 * startCoins) {
break;
}

calculateExpected();

int bestMachine = -1;

boolean isExplore = isExplore(time);
if (isExplore) {

double[] evPlusBonus = new double[numMachines];
for (int i = 0; i < evPlusBonus.length; i++) {
evPlusBonus[i] = expected[i] + bonus(i);
}
bestMachine = argMax(evPlusBonus);
if (countNote[bestMachine] >= 40) {
isExplore = false;
bestMachine = argMax(expected);
}

} else {
bestMachine = argMax(expected);
if (expected[bestMachine] < 1) {

isExplore = true;
double[] evPlusBonus = new double[numMachines];
for (int i = 0; i < evPlusBonus.length; i++) {
evPlusBonus[i] = expected[i] + bonus(i);
}
bestMachine = argMax(evPlusBonus);
}
}

if (coins <= 200) {
if (expected[argMax(expected)] > 1.1) {

} else {
break;
}
}

coins--;

int value;
String[] notePlay;
boolean useNote = isExplore;
useNote &= time + noteTime < maxTime;
if (useNote) {
notePlay = PlaySlots.notePlay(bestMachine, 1);
value = Integer.parseInt(notePlay[0]);
for (int c = 0; c < 3; c++) {
StringBuilder pattern = new StringBuilder();
for (int r = 0; r < 3; r++) {
pattern.append(notePlay[1].charAt(r * 3 + c));
}
if (wheelPatternSet[bestMachine][c].add(pattern.toString())) {
for (int r = 0; r < 3; r++) {
wheels[bestMachine][c].add(notePlay[1].charAt(r * 3 + c));
}
}
}
countNote[bestMachine]++;
} else {
value = PlaySlots.quickPlay(bestMachine, 1);
}
coins += value;
if (useNote) {
time += noteTime;
} else {
time++;
}
}

calculateExpected();

Utils.debug("expected", toString(expected));
}

private String[] toString(double[] expected2) {
String[] s = new String[expected2.length];
for (int i = 0; i < s.length; i++) {
s[i] = String.format("%.2f", expected2[i]);
}
return s;
}

private static int argMax(double[] values) {
int maxIndex = -1;
double max = -1e99;
for (int i = 0; i < values.length; i++) {
if (values[i] > max) {
max = values[i];
maxIndex = i;
}
}
return maxIndex;
}

private boolean isExplore(int time) {
double nextDouble = rng.nextDouble();
double range = updateRange(1.0, (double) 0, (double) time, (double) 0, (double) (maxTime - 1), 1.0);
return nextDouble < range;
}

private double bonus(int machine) {
if (countNote[machine] > 40) {
return 0;
}
return updateRange(2.0, 0, countNote[machine], 0, 40, 2.0);
}

private double updateRange(double startRange, double endRange, double time, double startTime, double endTime, double exp) {
if (time > endTime) {
return 0;
}
return endRange + (startRange - endRange) * Math.pow((endTime - time) / (endTime - startTime), exp);
}

private int count(ArrayList<Character> s, char c) {
int ret = 0;
for (int i = 0; i < s.size(); i++) {
if (s.get(i) == c)
ret++;
}
return ret;
}

private void calculateExpected() {
int[] payouts = new int[numMachines];
expected = new double[numMachines];
for (int i = 0; i < numMachines; i++) {
payouts[i] += count(wheels[i][0], 'A') * count(wheels[i][1], 'A') * count(wheels[i][2], 'A') * 1000;
payouts[i] += count(wheels[i][0], 'B') * count(wheels[i][1], 'B') * count(wheels[i][2], 'B') * 200;
payouts[i] += count(wheels[i][0], 'C') * count(wheels[i][1], 'C') * count(wheels[i][2], 'C') * 100;
payouts[i] += count(wheels[i][0], 'D') * count(wheels[i][1], 'D') * count(wheels[i][2], 'D') * 50;
payouts[i] += count(wheels[i][0], 'E') * count(wheels[i][1], 'E') * count(wheels[i][2], 'E') * 20;
payouts[i] += count(wheels[i][0], 'F') * count(wheels[i][1], 'F') * count(wheels[i][2], 'F') * 10;
payouts[i] += count(wheels[i][0], 'G') * count(wheels[i][1], 'G') * count(wheels[i][2], 'G') * 5;
if (payouts[i] == 0) {
expected[i] = 0;
continue;
}
expected[i] = 1.0 * payouts[i] / wheels[i][0].size() / wheels[i][1].size() / wheels[i][2].size();
}
}

private void init(int coins, int maxTime, int noteTime, int numMachines) {
this.coins = coins;
this.startCoins = coins;
this.maxTime = maxTime;
this.noteTime = noteTime;
this.numMachines = numMachines;
wheels = new ArrayList[numMachines][3];
for (int i = 0; i < wheels.length; i++) {
for (int j = 0; j < 3; j++) {
wheels[i][j] = new ArrayList<Character>();
}
}

wheelPatternSet = new HashSet[numMachines][3];
for (int i = 0; i < wheelPatternSet.length; i++) {
for (int j = 0; j < 3; j++) {
wheelPatternSet[i][j] = new HashSet<String>();
}
}

countNote = new int[numMachines];
Utils.debug("coins", coins, "maxTime", maxTime, "noteTime", noteTime, "numMachines", numMachines);
}

public static void main(String[] args) {
try {
BR.br = new BufferedReader(new InputStreamReader(System.in));

int coins = BR.readInt();
int maxTime = BR.readInt();
int noteTime = BR.readInt();
int numMachines = BR.readInt();

BrokenSlotMachines pw = new BrokenSlotMachines();
int ret = pw.playSlots(coins, maxTime, noteTime, numMachines);

System.out.println(ret);
System.out.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
}

final class Utils {
private Utils() {
}

public static final void debug(Object... o) {
System.err.println(toString(o));
}

public static final String toString(Object... o) {
return Arrays.deepToString(o);
}

}

class XorShift {
private int w = 88675123;
private int x = 123456789;
private int y = 362436069;
private int z = 521288629;

public XorShift(long l) {
x = (int) l;
}

public int nextInt() {
final int t = x ^ (x << 11);
x = y;
y = z;
z = w;
w = w ^ (w >>> 19) ^ (t ^ (t >>> 8));
return w;
}

public long nextLong() {
return ((long) nextInt() << 32) ^ (long) nextInt();
}

public double nextDouble() {
return (nextInt() >>> 1) * 4.6566128730773926E-10;
}

public int nextInt(int n) {
return (int) (n * nextDouble());
}

}


このページのトップヘ