Livearchive 5906 Smoking gun
問題
n人の人が平面上にいて、それぞれの座標が与えられる。
これらの人が、合計でm個、次のような目撃証言をした。
「私は、a[i]がb[i]よりも先に銃を撃つのを聞いた」
音の進む速さは有限であるので、
先に銃を撃つ音が聞こえた者が、先に発砲したとは限らない。
このとき、発砲した人の順序が、矛盾しているならIMOPSSIBLEを、
一意に定まるならばその順序を、そうでなければ、UNKNOWNを出力せよ。
制約条件
n≦100
m≦1000
方針
人kが、jより先にiが発砲したと証言したとき、
d(k, i)をd1, d(k, j)をd2とすれば、
iの発砲時刻<jの発砲時刻 + d2 - d1という不等式が立つ。
それぞれの証言は、一つの不等式制約になる。
不等式制約の双対を取ると、最短路問題になる(蟻本の牛の距離の問題のやつ)ので、
最短路問題にして考える。
t[i] < t[j] + xが成り立つとき、
jからiに、コストxの辺を張ればいい。
そして、グラフに非負の閉路があれば矛盾である。
グラフを作り終えたら、ワーシャルフロイドをして、全点間の最短距離を求める。
ここから、一番最初に発砲した人を決定していけばいい。
一番最初に発砲した人は、
全てのjについて、d[j][i] < 0となる人iである。
なぜなら、d[j][i] < 0のとき、i < j - xとなっているはずだから。
iがわかったら、iを取り除いて同様に考える。
iが一意に定まらなかったら、UNKOWN.
ソースコード
int n, m; string name[100]; double x[100], y[100], d[100][100]; bool use[100]; inline double dist(int a, int b){ double d2 = (x[a] - x[b]) * (x[a] - x[b]) + (y[a] - y[b]) * (y[a] - y[b]); return sqrt(d2); } int main() { int cs; cin >> cs; while(cs--){ cin >> n >> m; map<string, int> id; rep(i, n){ cin >> name[i] >> x[i] >> y[i]; id[name[i]] = i; use[i] = 0; } rep(i, n) rep(j, n) d[i][j] = INF; rep(i, m){ string a, tmp, c, f; cin >> a >> tmp >> c >> tmp >> tmp >> f; int p = id[a], q = id[c], r = id[f]; double d1 = dist(p, q), d2 = dist(p, r); d[r][q] = min(d[r][q], d2 - d1); use[q] = use[r] = 1; } rep(k, n) rep(i, n) if(use[i]) rep(j, n) if(use[j]) d[i][j] = min(d[i][j], d[i][k] + d[k][j]); bool end = 0; rep(i, n) if(use[i] && d[i][i] < EPS){ cout << "IMPOSSIBLE" << endl; end = 1; break; } if(end) continue; int k = 0; rep(i, n) if(use[i]) k++; vi ord; rep(it, k){ int mn = -1; rep(i, n) if(use[i]){ bool ok = 1; rep(j, n) if(i != j && use[j] && d[j][i] > EPS) ok = 0; if(ok) mn = i; } if(mn < 0){ cout << "UNKNOWN" << endl; goto END; } ord.pb(mn); use[mn] = 0; } rep(i, k) cout << name[ord[i]] << (i == k - 1 ? "\n" : " "); END:; } return 0; }