考察を楽しめました。
問題概要
https://atcoder.jp/contests/abc168/tasks/abc168_e
要素の整数の2つ組の列を与えます*1。列から一つ以上の要素を選び出す方法で、どの選んだ2要素についてもを満たすようなものの通り数をで割ったあまりを出力してください。
制約
考察
をのベクトル (言い方合ってますかね?)とみなすと。条件は内積がでないと言い換えることができます。
なのでの時との時を場合分けして考えることにします
の時
の場合に限ります。このようなを選んでしまった場合、他のどのような要素に対しても内積がになってしまい条件に違反します。よってこのようなを選ぶ時は他のベクトルを同時に選んではいけないことがわかります。
の時
いわゆる「ベクトルが直交する時」ですね。すべてのペアについてベクトルが直交しているか確かめるのはTLに間に合わないので工夫して2つのベクトルが直交するか確かめなければなりません。
ここで、二次元平面上で原点と点を通る直線を考えます。でないベクトル、が直交することは、対応する直線が直交することと同値です。よって直線毎に考えます。ベクトルが個しか無いのと、ある(原点を通る)直線と直交する(原点を通る)直線はちょうど一本しか無いので考えるべき直線は高々個しかありません。
ある列上2つのベクトルが同じ直線に対応する時、その2つのベクトルは同一のベクトルが2つあると考えて良いです。なぜなら
- その2つのベクトルは明らかに直交していない
- 片方のベクトルと直交する列上のベクトルの集合と、もう片方のベクトルと直交する列上のベクトルの集合は一致する
からです。
解法
以上より、以下のような手続きで条件を満たすような選び方を数え上げることができます。
- 考えるべき個の直線を予め列挙しておく、直線毎に対応するベクトルの数と直交する直線を高速に取り出せるようにしておく(連想配列等でできると思います)
- とする。それぞれの直線に対して探索済か否かを記憶する列を用意しておき、すべての直線に対して未探索としておく
- 未探索の直線を一本自由に選ぶ(Pとする)。選んだ直線と直交する直線(Qとする)を取り出す(そのような直線は必ず未探索である)
- P, Qに対応するベクトルの数をp, qとしてにを掛ける
- Pに対応する方から選ぶ通り数とQに対応する方から選ぶ通り数を掛けています。
- Pに対応するベクトルとQに対応するベクトルを同時にえらぶことはできないことに注意してください
- 二回空集合を数えているので、しています
- P, Qを探索済とする
- すべての直線が探索すみになるまで、3 ~ 5を繰り返す
- の分をに足す。最後にからを引く(空集合の分)
提出コード
- 実装がうまくできなくてかなりぐちゃぐちゃなコードになってしまいました
#include <bits/stdc++.h> #include <atcoder/modint> using i32 = int; using i64 = long long; using Point = std::pair<i64, i64>; using mint = atcoder::modint1000000007; using namespace std; void minify(i64& x, i64& y) { i64 g = gcd(x, y); if (g == 0) return; x /= g; y /= g; if (x < 0) { x *= -1; y *= -1; } if (x == 0 and y < 0) { // これ忘れててペナを連発してしまった y *= -1; } } void main_() { i32 N; cin >> N; map<Point, i32> buc; for (i32 _ = 0 ; _ < N ; _++) { i64 x, y; cin >> x >> y; minify(x, y); buc[{ x, y }]++; } map<Point, i32> idx; i32 sz = 0; for (const auto& [p, _] : buc) idx[p] = sz++; vector<Point> P; for (const auto& [p, _] : buc) P.push_back(p); // assert(P.size() == (size_t)sz); vector<i32> orh(sz, -1); for (i32 i = 0 ; i < sz ; i++) { auto [x, y] = P[i]; i64 rx = -y, ry = x; minify(rx, ry); if (idx.count({ rx, ry })) { orh[i] = idx[{ rx, ry }]; } } const i32 mN = 200010; vector<mint> pow2(mN, mint{1}); for (i32 i = 0 ; i + 1 < mN ; i++) { pow2[i + 1] = pow2[i] * 2; } mint ans{1}; vector<bool> used(sz, false); set<i32> st; for (i32 i = 0 ; i < sz ; i++) { if (used[i]) continue; if (P[i] == Point{ 0LL, 0LL }) continue; if (orh[i] == -1) { ans *= pow2[buc[P[i]]]; used[i] = true; } else { assert(i < orh[i]); // assert(orh[i] != i); // assert(used[orh[i]] == false); ans *= pow2[buc[P[i]]] + pow2[buc[P[orh[i]]]] - 1; used[i] = used[orh[i]] = true; } } ans += buc[{ 0LL, 0LL }]; ans -= 1; // assert(count(used.begin(), used.end(), true) == (i32)used.size()); cout << ans.val() << endl; } int main() { cin.tie(nullptr)->sync_with_stdio(false); main_(); return 0; }