graph.h
#pragma once
#include <algorithm>
#include <cassert>
#include <iostream>
#include <queue>
#include <set>
#include <unordered_set>
#include "Instance.h"
using namespace std;
/* Usage:
* - initialize with `graph g(ins, [max time horizon]);` default max time horizon is ins.maxt
* - if you make changes to the graph that you want to push to the instace,
* call `g.update_instance();`
* - if you make changes to underlying instance you want to update graph with,
* call `g.build([max time horizon])`
* - g.robots() stores the last known location of the robot
*/
struct node {
int r; // robot (-1 means obstacle, 0 means empty, i means i-1th robot
int d; // direction that robot went in
node(int robot = 0, int dir = 0) : r(robot), d(dir) {}
void reset() { r = d = 0; }
};
struct graph {
// Assume coordinates range from [-OFF, DIM-OFF]
const int DIM = 500;
const int OFF = 200;
int maxt;
instance& ins;
vector<node> g;
vector<int> robots;
// hash state to single int for convenience
inline int hsh(int x, int y, int t) {
return (x + OFF) + (y + OFF) * DIM + t * DIM * DIM;
}
inline int hsh(pt p, int t) { return hsh(p.x, p.y, t); }
// unhash state
inline int get_x(int h) { return h % DIM - OFF; }
inline int get_y(int h) { return (h % (DIM * DIM )) / DIM - OFF; }
inline pt get_p(int h) { return pt(get_x(h), get_y(h)); }
inline int get_t(int h) { return h / (DIM * DIM); }
inline bool in_range(const pt& p) {
if (p.x <= -OFF || p.x >= DIM - OFF || p.y <= -OFF || p.y >= DIM - OFF) {
return false;
}
return true;
}
// memoized robotless distance function
vector<vector<int>> mem_dist;
int dist(pt p, int r) {
if(!mem_dist[r].empty()) return mem_dist[r][hsh(p, 0)];
mem_dist[r].resize(DIM * DIM, INF);
int start = hsh(ins.target[r], 0);
mem_dist[r][start] = 0;
queue<int> q;
q.push(start);
while(!q.empty()) {
int h = q.front();
q.pop();
pt p = get_p(h);
for(int i = 1; i <= 4; i++) {
pt np = p + dxy[i];
int nh = hsh(np, 0);
if (!in_range(np) || g[nh].r==-1) continue;
if (mem_dist[r][nh] > mem_dist[r][h]+1) {
mem_dist[r][nh] = mem_dist[r][h]+1;
q.push(nh);
}
}
}
return dist(p, r);
}
// initialize graph
void build(int t = -1) {
maxt = max(t+1, (int)ins.moves.size()+1);
g.clear();
mem_dist.clear();
mem_dist.resize(ins.n);
g.resize(DIM * DIM * maxt, node());
robots.resize(ins.n);
// fill obstacles
for (pt& p : ins.obstacle) {
for (int t = 0; t < maxt; t++) {
g[hsh(p, t)].r = -1;
}
}
// fill paths
vector<pt> locations = ins.start;
int curt = 0;
for (auto& step : ins.moves) {
for (int i = 0; i < ins.n; ++i) {
pt p = locations[i];
g[hsh(p, curt)].r = i + 1;
g[hsh(p, curt)].d = step[i];
p = p + dxy[step[i]];
locations[i] = p;
if (!in_range(p)) {
cerr << "ERROR : Out of range coordinate considered " << p.x << " "
<< p.y << endl;
cerr << "Consider changing limits in the code." << endl;
assert(false);
}
}
curt++;
}
for (; curt < maxt; ++curt) {
for (int i = 0; i < ins.n; ++i) {
pt p = locations[i];
g[hsh(p, curt)].r = i + 1;
g[hsh(p, curt)].d = 0;
}
}
for (int i = 0; i < ins.n; ++i) {
robots[i] = hsh(locations[i], maxt-1);
}
}
graph(instance& _ins, int _maxt = -1) : ins(_ins) {
build(_maxt);
}
// checks if moving from p -> np at time t is valid (assumes moving robot is not in the graph)
bool valid_move(const pt& p, const pt& np, const int t) {
if (t + 1 == (int)maxt)
return true;
// check whether p->np is valid
int ch = hsh(np, t);
int nh = hsh(np, t + 1);
pt dir = np - p;
if (g[nh].r != 0) { // some other robot or obstacle is there at next time step
return false;
}
if (g[ch].r != 0) { // some other robot or obstacle is there now
if (dxy[g[ch].d] != dir) { // if its not going the same direction as us
return false;
}
}
// check whether something moving ->p is still valid
nh = hsh(p, t + 1);
if (g[nh].r != 0) { // some robot is following behind, into the same spot
if (g[nh].r == -1) {
cerr << "wait how was this robot on an obstacle ??? " << endl;
assert(false);
}
int otherd = -1;
for (int d = 1; d < 5; d++) { // figure out where it came from
if (g[hsh(p + dxy[d], t)].r == g[nh].r) {
otherd = opposite_dir[d];
}
}
if (dir != dxy[otherd]) {
return false;
}
}
return true;
}
bool find_best_time(int r, int startt = 0, bool ignore_regressions = false) {
int old_moves = remove_path(r, startt);
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
vector<int> dis(g.size(), INF); // best distance
vector<int> pre(g.size(), -1); // previous move
int start = robots[r];
int target = hsh(ins.target[r], maxt - 1); // target hash
dis[start] = dist(get_p(start), r);
pq.push({dis[start], start});
bool found = false;
while (!pq.empty()) {
int cost = pq.top().first;
int h = pq.top().second;
if (h == target) {
found = true;
break;
}
pq.pop();
if (dis[h] < cost) continue;
pt p = get_p(h);
int t = get_t(h);
int nt = t + 1;
for (int dir = 0; dir < 5; dir++) {
pt np = p + dxy[dir];
int nh = hsh(np, nt);
int ncost = cost + (1 - dist(p, r) + dist(np, r));
if (nt < maxt && in_range(np) && dis[nh] > ncost && valid_move(p, np, t)) {
dis[nh] = ncost;
pq.push({dis[nh], nh});
pre[nh] = dir;
}
}
}
int moves = 0;
if (!found) {
cerr << "NO PATH FOUND, PLEASE FIX YOUR SEED INSTANCE" << endl;
return false;
}
else {
// recover path backwards
robots[r] = target;
g[target].r = r + 1;
g[target].d = 0;
int cur = target;
while (cur != start) {
pt p = get_p(cur);
int t = get_t(cur);
int pdir = pre[cur];
if (pdir) moves++;
pt pp = p - dxy[pdir];
auto pt = t - 1;
int pcur = hsh(pp, pt);
g[pcur] = node(r + 1, pdir);
cur = pcur;
}
/*
if (startt == 0 && old_moves > moves) {
cerr << "Improved robot r = " << r << ", from " << old_moves
<< " moves to " << moves << " moves." << "(+" << old_moves - moves << ") " << endl;
} else if (startt == 0 && old_moves < moves) {
if (ignore_regressions) {
cerr << "Robot regression r = " << r << ", from " << old_moves
<< " moves to " << moves << " moves." << "(" << old_moves - moves << ") " << endl;
}
else {
cerr << "THIS IS BAD, FAILING robot r = " << r << ", from " << old_moves
<< " moves to " << moves << " moves." << "(" << old_moves - moves << ") " << endl;
assert(false);
}
}*/
}
return old_moves > moves;
}
// find shortest path for robot r, starting at time t, keeping all else before time t fixed
// returns if improved
bool find_best(int r, int startt = 0, bool ignore_regressions = false) {
int old_moves = remove_path(r, startt);
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
vector<int> dis(g.size(), INF); // best distance
vector<int> pre(g.size(), -1); // previous move
int start = robots[r];
int target = hsh(ins.target[r], maxt - 1); // target hash
dis[start] = dist(get_p(start), r);
pq.push({dis[start], start});
bool found = false;
while (!pq.empty()) {
int cost = pq.top().first;
int h = pq.top().second;
if (h == target) {
found = true;
break;
}
pq.pop();
if (dis[h] < cost) continue;
pt p = get_p(h);
int t = get_t(h);
pt np = p;
int nt = t + 1;
int nh = hsh(np, nt);
if (nt < maxt && dis[nh] > cost && valid_move(p, np, t)) {
dis[nh] = cost;
pre[nh] = 0;
pq.push({dis[nh], nh});
}
for (int dir = 1; dir < 5; dir++) {
np = p + dxy[dir];
nh = hsh(np, nt);
int ncost = cost + (1 - dist(p, r) + dist(np, r));
if (nt < maxt && in_range(np) && dis[nh] > ncost && valid_move(p, np, t)) {
dis[nh] = ncost;
pq.push({dis[nh], nh});
pre[nh] = dir;
}
}
}
int moves = 0;
if (!found) {
cerr << "NO PATH FOUND, PLEASE FIX YOUR SEED INSTANCE" << endl;
return false;
}
else {
// recover path backwards
robots[r] = target;
g[target].r = r + 1;
g[target].d = 0;
int cur = target;
while (cur != start) {
pt p = get_p(cur);
int t = get_t(cur);
int pdir = pre[cur];
if (pdir) moves++;
pt pp = p - dxy[pdir];
auto pt = t - 1;
int pcur = hsh(pp, pt);
g[pcur] = node(r + 1, pdir);
cur = pcur;
}
/*
if (startt == 0 && old_moves > moves) {
cerr << "Improved robot r = " << r << ", from " << old_moves
<< " moves to " << moves << " moves." << endl;
} else if (startt == 0 && old_moves < moves) {
if (ignore_regressions) {
//cerr << "Robot regression r = " << r << ", from " << old_moves
// << " moves to " << moves << " moves." << endl;
}
else {
cerr << "THIS IS BAD, FAILING robot r = " << r << ", from " << old_moves
<< " moves to " << moves << " moves." << endl;
assert(false);
}
}*/
}
return old_moves > moves;
}
// Verifies if path taken by robot r goes from source to target is valid
// currently broken, please don't use!
bool verify_path(int r, bool check_target = false) {
pt p = ins.start[r];
int lastt = get_t(robots[r]);
for (int t = 0; t < lastt; t++) {
if (g[hsh(p, t)].r != r + 1) {
// something seriously went wrong
cerr << "INVALID STATE AT " << t << " FOR ROBOT " << r << endl;
assert(false);
}
int dir = g[hsh(p, t)].d;
if (!valid_move(p, p + dxy[dir], t)) {
return false;
}
if (dir) {
p = p + dxy[dir];
}
}
return (!check_target || p == ins.target[r]);
}
// remove path taken of robot r, returns length of path for reasons
int remove_path(int r, int time = 0) {
pt p = ins.start[r];
int lastt = get_t(robots[r]);
int moves = 0;
vector<int> mv;
for (int t = 0; t <= lastt; t++) {
int dir = g[hsh(p, t)].d;
if (t >= time) {
g[hsh(p, t)].reset();
if (t == time) robots[r] = hsh(p, t);
}
mv.push_back(dir);
if (dir) {
moves++;
p = p + dxy[dir];
}
}
return moves;
}
void update_instance() {
cerr << "UPDATING INSTANCE " << endl;
ins.moves.clear();
vector<vector<int>> vv(maxt, vector<int>(ins.n, 0));
for (int r = 0; r < ins.n; r++) {
pt pos = ins.start[r];
int lastt = get_t(robots[r]);
for (int t = 0; t < maxt; t++) {
int d = 0;
if (t < lastt) {
assert(g[hsh(pos, t)].r == r + 1);
d = g[hsh(pos, t)].d;
}
vv[t][r] = d;
pos = pos + dxy[d];
}
}
vector<int> zeroes(ins.n, 0);
// trim still frames
for (auto& v : vv)
if (v != zeroes)
ins.moves.push_back(v);
ins.time = ins.moves.size();
}
};