Official

E - Trapezium Editorial by sheyasutaka


大まかな方針

まず,平行な辺の組が何通りあるかを数えることを考える.これは,\(\frac{N(N-1)}{2}\) 通りある \(2\) 点の選び方それぞれについてその傾きを計算し,ありうるすべての傾きの値 \(x\) について以下を行えばよい.

  • 傾きが \(x\) であるような \(2\) 点の選び方の個数を \(c_x\) とする.このとき,\(\frac{c_x(c_x-1)}{2}\) を答えに加える.

ここで,平行四辺形であるような台形は \(2\) 回,そうでない台形は \(1\) 回数えられている.したがって,平行四辺形の個数を答えから引く必要がある.これは,\(2\) つの対角線の中点が一致することが平行四辺形であることと同値なので,\(\frac{N(N-1)}{2}\) 通りある \(2\) 点の選び方それぞれについてその中点を計算し,ありうるすべての中点の値 \(y\) について以下を行えばよい.

  • 中点が \(y\) であるような \(2\) 点の選び方の個数を \(d_y\) とする.このとき,\(\frac{d_y(d_y-1)}{2}\) を答えから引く.

これによって,すべての台形がちょうど \(1\) 回ずつ数えられた状態になり,正しい答えが得られる.

実装

傾きを浮動小数点数で計算した場合,計算誤差によって一致判定を誤る可能性があるほか,\(1/0\)\(-1/0\) の計算に困難が生じる.有理数の形式で計算すれば,一致判定が容易である.

詳細は以下の実装例を参照してほしい.

実装例 (C++)

#include <iostream>
#include <cstdio>
#include <vector>
#include <array>
#include <set>
#include <map>
using std::cin;
using std::cout;
using std::cerr;
using std::endl;
using std::pair;
using std::make_pair;
using std::vector;
using std::min;
using std::max;
using std::array;
using std::set;
using std::map;

#include <atcoder/all>
using mint = atcoder::modint998244353;
using atcoder::segtree;

typedef long long int ll;

ll n;
vector<ll> x, y;

ll mygcd (ll a, ll b) {
	if (b == 0) return a;
	return mygcd(b, a % b);
}
ll myabs (ll x) {
	return (x >= 0) ? x : (-x);
}

vector<pair<ll, ll> > slopes;
vector<pair<ll, ll> > centers;
ll countsamepair (const vector<pair<ll, ll> > &vec) {
	ll sum = 0;
	ll idx = 0;
	while (idx < vec.size()) {
		ll j = idx;
		while (j < vec.size() && vec[j] == vec[idx]) j++;

		ll len = j - idx;
		sum += len * (len-1) / 2;

		idx = j;
	}
	return sum;
}
void solve () {
	for (ll i = 0; i < n; i++) {
		for (ll j = i+1; j < n; j++) {
			// slope
			ll px = x[j] - x[i];
			ll py = y[j] - y[i];

			if (px == 0) {
				px = 0;
				py = 1;
			} else if (py == 0) {
				px = 1;
				py = 0;
			} else {
				if (px < 0) {
					px *= -1;
					py *= -1;
				}
				ll g = mygcd(myabs(px), myabs(py));
				px /= g;
				py /= g;
			}

			slopes.push_back({px, py});

			// center
			centers.push_back({x[i]+x[j], y[i]+y[j]});
		}
	}

	ll ans = 0;
	sort(slopes.begin(), slopes.end());
	ans += countsamepair(slopes);
	sort(centers.begin(), centers.end());
	ans -= countsamepair(centers);

	cout << ans << "\n";

	
	return;
}

int main (void) {
	std::ios_base::sync_with_stdio(false);
	cin.tie(nullptr);
	
	cin >> n;
	x.resize(n);
	y.resize(n);
	for (ll i = 0; i < n; i++) {
		cin >> x[i] >> y[i];
	}

	solve();

	return 0;
}

posted:
last update: