僕の以前書いた記事、共円がちょっと関係ある問題だ!!!!!!!!!!!!!
ガウス整数!!!!!!!!!!!!!!!!!!!
問題概要
二次元平面に関する以下の 3 種類のクエリ ( 個) に答えよ
- 格子点 に石をおく
- 格子点 においてある石を取り除く
- 格子点 が指定されて、盤面全体の石の配置が 2 点 と とを通る直線について線対称になるように、石を追加で置くことを考えたとき、そのために必要な石の個数を答えよ (実際に石を置くわけではない)
制約
考えたこと
と とが、ある原点を通る直線について線対称であるとき、
が成立する。そこで、問題を、各整数 に対して
ごとに独立に解くことを考える。そして今回は最悪でも 程度であり、この範囲内ではこれを満たす正の整数 の個数が 以下であることが示せる (共円でいうところの 576 角形定石)。
に書いた複素整数の理論を使うとそれが示せる。結論として、
正の整数 に対して の解が存在する必要十分条件は、 を素因数分解したとき、すべての 4 で割って 3 あまる素因子について指数が偶数であることである。
またこの条件を満たすときの解の個数は、 を素因数分解したときの 4 で割って 1 あまる素因子の部分だけを とするとき、 個である。
ということが示せる。
さて、各整数 に対して を満たす正の整数 が十分少ないことを利用した解法を考える。
- points[N]: ノルムが な格子点を set に突っ込んだもの
- nums[{x, y}]: 盤上の石のうち、2 点 と とを通る直線に対して相方がいるような点の個数 (ここで は既約にしておく)
- num: 置かれている点の個数
として、これらはクエリ 1, 2 に対して高々 144 個程度の更新でできる。クエリ 3 に対しては
- num - nums[{x, y}]
を答えればよい ( は既約にする)。
#include <iostream> #include <map> #include <set> #include <vector> using namespace std; using pll = pair<long long, long long>; long long GCD(long long a, long long b) { if (b == 0) return a; else return GCD(b, a % b); } void normalize(long long &a, long long &b) { long long g = GCD(a, b); a /= g, b /= g; } int main() { int Q; scanf("%d", &Q); map<long long, set<pll> > points; map<pll, int> nums; long long num = 0; for (int _ = 0; _ < Q; ++_) { int t; long long x, y; scanf("%d %lld %lld", &t, &x, &y); if (t == 1) { long long N = x*x + y*y; for (auto p : points[N]) { long long nx = p.first + x; long long ny = p.second + y; normalize(nx, ny); nums[pll(nx, ny)] += 2; } points[N].insert(pll(x, y)); normalize(x, y); nums[pll(x, y)]++; ++num; } else if (t == 2) { long long N = x*x + y*y; points[N].erase(pll(x, y)); for (auto p : points[N]) { long long nx = p.first + x; long long ny = p.second + y; normalize(nx, ny); nums[pll(nx, ny)] -= 2; } normalize(x, y); nums[pll(x, y)]--; --num; } else { normalize(x, y); printf("%lld\n", num - nums[pll(x, y)]); } } }