AtCoder 418-E
这道题也太烦了。。。
\(\space\space\space\) ↓
链接在此
题面:
在网格图上给定 \(n\) 个点, 保证不存在三点一线,请问能选出多少组数字\((a,b,c,d)\)满足 \(1\leq a < b < c < d \leq n\) 并且 点\(a\) 点\(b\) 点\(c\) 点\(d\) 恰好构成梯形(不能是平行四边形,长方形等)
思路:
首先我们想想梯形的要求:两条线平行,另外两条线不平行。
那么我们可以用\(n^2\)的时间复杂度求出这些点两两连线后的斜率,每种斜率统计一下数量,最后对于每种斜率,排列组合求选出2条线的方案数,最后累加,即可求出能连成有一组平行线的图形的方案数,这样就好了。。。。吗?
nonono!不对!我们还要判断不能是平行四边形呢!怎样会形成平行四边形呢?如果两条平行线长度相等,那么就会产生平行四边形。
所以:我们统计每种斜率和长度的出现次数,对于每一种斜率和长度,也用排列组合求选出2条线的方案数,最后累加即可求出有多少平行四边形。
那么就可以做了。。。
代码:
#include <bits/stdc++.h>
using namespace std;
map<double, int> mp;
map<pair<double, double>, int > mp2;
int x[2001];
int y[2001];
double dis(int x1, int y1, int x2, int y2)
{
return sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
}
int main()
{
int n;
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> x[i] >> y[i];
}
for (int i = 1; i <= n; i++)
{
for (int j = i + 1; j <= n; j++)
{
double t = (x[j] - x[i]) * 1.0 / (y[j] - y[i]); // 计算斜率
double len = dis(x[i], y[i], x[j], y[j]); // 计算长度
mp2[ { t, len } ]++; // 统计长度和斜率
mp[t]++; // 统计斜率
}
}
long long ans = 0;
for (auto it : mp)
{
int k = it.second;
ans += 1ll * k * (k - 1 ) / 2; // 有至少一组平行线
}
long long xx = 0;
for (auto it : mp2)
{
int k = it.second;
xx += 1ll * k * (k - 1) / 2; // 排除掉平行四边形(同时也排除掉了长方形等非法四边形)
}
cout << ans - xx / 2 << endl; // 注意由于每个平行四边形都会有两组平行线,所以会被算两次,所以真正的数量是要除以2的
return 0;
}
提交过后可以惊喜的发现:AC:10 WA:18
这是什么问题呢?
其实啊,这是因为double精度不够,那么该怎么解决呢?
首先是长度:注意到根号导致了问题,那么可以拆掉根号,这并不影响,因为我们只要知道两条线长度是否相等,不需要知道确定值,所以同时拆掉根号是不影响的
那么怎么改掉斜率呢?由于是除号发生了问题,我们可以直接记录分母和分子,注意由于可能不同的分母和分子能计算出相同的结果(例如\(\frac{1}{2}\)和\(\frac{2}{4}\)),所以要把除法算式(分数)化简成最简的。
注意长度由于拆了根号,可能会爆int,要用longlong。
代码:
#include <bits/stdc++.h>
using namespace std;
map<pair<int, int>, int> mp;
map<pair<pair<int, int>, long long>, int > mp2;
int x[2001];
int y[2001];
long long dis(int x1, int y1, int x2, int y2)
{
return 1ll * (x1 - x2) * (x1 - x2) + 1ll * (y1 - y2) * (y1 - y2); // 注意longlong
}
int main()
{
int n;
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> x[i] >> y[i];
}
for (int i = 1; i <= n; i++)
{
for (int j = i + 1; j <= n; j++)
{
int t1 = x[j] - x[i];
int t2 = y[j] - y[i];
int d = __gcd(t1, t2); // 化成最简分数
t1 /= d;
t2 /= d;
int len = dis(x[i], y[i], x[j], y[j]); // 长度
mp2[ { { t1, t2 }, len } ]++; // 统计
mp[ { t1, t2 } ]++; // 统计
}
}
long long ans = 0;
for (auto it : mp)
{
int k = it.second;
ans += 1ll * k * (k - 1) / 2; // 有(至少)一组平行线的图形的个数
}
long long xx = 0;
for (auto it : mp2)
{
int k = it.second;
xx += 1ll * k * (k - 1) / 2; // 平行四边形的个数
}
cout << ans - xx / 2 << endl;
return 0;
}
于是你拿到了:AC: 28 WA: 0
完结撒花★.☆(w)/$★ 。

浙公网安备 33010602011771号