SRM645D1Med ArmyTeleportation
これこそ 600ptsだと思う
問題概要(原文)
二次元平面上の格子点に置かれた $N$ 個の区別できない石を,別の $N$ 箇所に移動させたい. 可能な操作は $(xt_i,yt_i)(1 \leq i \leq 3)$ に対称な点に移動させるのみ. 可能かどうか判定せよ.
考察
$2$ 回の操作を行うと $(2(xt_j-xt_i),2(yt_j-yt_i))$ だけ平行移動できる.さらに,平行移動は $3$ 種類ありそうに見えるが, $1$ つは他のベクトルの合成で表せるので不要.つまりこの問題はこう言い換えられる.
- $0$ 回,または $1$ 回の対称移動と, $2$ 種類の平行移動を任意回繰り返して,目標の位置に移動させることは可能か?
さて,$1$ 回の対称移動は簡単にできる.全体を並行移動させて一致するかどうかの判定は $x$ 座標についてソートして調べればよい. 最後は $p \times vx_1 + q \times vx_2 = dx, p \times vy_1+ q \times vy_2 = dy$ を満たす,整数 $p,q$ があるか判定する問題が解ければよい.
幸い,$|dx|,|dy| \leq 10^7$ なので, $p$ について全探索できる.
ソースコード
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Debug = System.Diagnostics.Debug;
using StringBuilder = System.Text.StringBuilder;
public class ArmyTeleportation
{
public string ifPossible(int[] x1, int[] y1, int[] x2, int[] y2, int[] xt, int[] yt)
{
return f(x1, y1, x2, y2, xt, yt) ? "possible" : "impossible";
}
bool f(int[] x1, int[] y1, int[] x2, int[] y2, int[] xt, int[] yt)
{
var n = x1.Length;
var vx = new List<int>();
var vy = new List<int>();
for (int i = 0; i < 2; i++)
{
vx.Add(2 * (xt[2] - xt[i]));
vy.Add(2 * (yt[2] - yt[i]));
}
for (int i = 0; i < vx.Count; i++)
Debug.WriteLine("{0} {1}", vx[i], vy[i]);
if (g(x1, x2, y1, y2, vx, vy)) return true;
for (int i = 0; i < 3; i++)
{
var X = x1.ToArray();
var Y = y1.ToArray();
for (int j = 0; j < n; j++)
{
X[j] = 2 * xt[i] - x1[j];
Y[j] = 2 * yt[i] - y1[j];
}
if (g(X, x2, Y, y2, vx, vy)) return true;
}
return false;
}
bool g(int[] x1, int[] x2, int[] y1, int[] y2, List<int> vx, List<int> vy)
{
int n = x1.Length;
Array.Sort(x1, y1);
Array.Sort(x2, y2);
var dx = x2[0] - x1[0];
var dy = y2[0] - y1[0];
for (int j = 0; j < n; j++)
{
if (dx != x2[j] - x1[j])
return false;
if (dy != y2[j] - y1[j])
return false;
}
Debug.WriteLine("{0} {1}", dx, dy);
for (long i = -5000000; i < 5000000; i++)
{
var u = dx + i * vx[0];
var v = dy + i * vy[0];
if (u != 0 && vx[1] == 0) continue;
if (v != 0 && vy[1] == 0) continue;
if (vx[1] == 0) return v % vy[1] == 0;
if (vy[1] == 0) return u % vx[1] == 0;
if (u % vx[1] != 0) continue;
if (v % vy[1] != 0) continue;
if (u / vx[1] == v / vy[1]) return true;
}
return false;
}
}
コメント
- 対称移動を $2$ 回すると平行移動になる
- 連立方程式が整数解を持つかどうかで判定する
- 今回は全探索でも解けるが…
- 全体としては典型問題の組み合わせだけど面白い方だと思う