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)/$

posted @ 2025-08-12 15:38  MichaelZeng  阅读(6)  评论(0)    收藏  举报