P7676 COCI2013-2014#5 TROKUTI
P7676 COCI2013-2014#5 TROKUTI - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
首先考虑三角形的形成条件(注意到题面保证了无三线共点):
- 三条边;
- 任意两条边不平行。
考虑补集,统计存在两条边平行的三元组数量,用总数 \(\dbinom{n}{3}\) 减去即可。
存在两条边平行,也就是满足以下两种情况之一:
- 两条边平行,另一条边不与这两条边平行;
- 三条边均平行。
两条直线平行当且仅当它们的斜率相同(竖线可看做 \(+\infty\)),那么我们统计每种斜率出现的数量 \(c\)。
我们对于每种斜率都进行如下统计:
- \(c \ge 2\) 时,数量 \(\dbinom{c}{2} \times (n - c)\),代表在斜率为 \(c\) 的这些平行直线中选取两条,和另外一条与其斜率不同的直线组成的三元组数量;
- \(c \ge 3\) 时,可以构成满足条件二的三角形,数量 \(\dbinom{c}{3}\),代表斜率为 \(c\) 的这些平行直线中选取三条组成的三元组数量。
这样每个不合法的三元组都可以被不重不漏统计一次。
至于如何统计每种斜率出现的数量,应该采用 map。
时间复杂度 \(\mathcal{O}(n \log V)\),\(V\) 是 \(a_i, b_i\) 的值域。
当然我这里采用的是记录 gcd 约分后的 \(a_i, b_i\) 的方法,如果直接记录斜率的实数值时间复杂度可以达到 \(\mathcal{O}(n\log n)\)。
#include <bits/stdc++.h>
#define int long long
inline int read() {
int x = 0;
bool flag = true;
char ch = getchar();
while (!isdigit(ch)) {
if (ch == '-')
flag = false;
ch = getchar();
}
while (isdigit(ch)) {
x = (x << 1) + (x << 3) + ch - '0';
ch = getchar();
}
if(flag)
return x;
return ~(x - 1);
}
const int mod = (int)1e9 + 7;
const int maxn = 300005;
int a[maxn], b[maxn];
inline int C3(int x) {
return x * (x - 1) / 2 * (x - 2) / 3;
}
inline int C2(int x) {
return x * (x - 1) / 2;
}
int gcd(int a, int b) {
return b ? gcd(b, a % b) : a;
}
typedef std :: pair <int, int> pii;
std :: map <pii, int> m;
signed main() {
int n = read();
for (int i = 1; i <= n; ++i) {
int a = read(), b = read();
read();
if (a == 0)
b = 1;
else if (b == 0)
a = 1;
else {
int d = gcd(a, b);
a /= d;
b /= d;
}
++m[std :: make_pair(a, b)];
}
int ans = C3(n) % mod;
for (auto it : m) {
int cnt = it.second;
if (cnt >= 2)
ans -= C2(cnt) * (n - cnt) % mod;
if (cnt >= 3)
ans -= C3(cnt) % mod;
(ans += mod) %= mod;
}
printf("%lld\n", ans);
return 0;
}