2019CCPC秦皇岛 A. Angle Beats

本文介绍了一种高效计算平面直角三角形数量的方法,通过预处理斜率并使用哈希表或二分查找来加速查找过程。适用于点集查询场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

传送门

思路

分类讨论

  1. 当询问的点是直角三角形的直角的顶点时,预处理N个点和询问的点连成的直线的斜率,枚举每个直线,查找有多少个与当前直线垂直的直线(斜率相乘为-1)
  2. 当询问的点时直角三角形的非直角顶点时,也就是直角顶点在给出的N个点上,这个时候可以预处理N个点之间的直线斜率,然后枚举询问的点和N个点的直线,查找枚举的点上有多少条直线和当前枚举的直线垂直



思路很好想,主要是如何快速,准确的查找到给定斜率的直线有多少条



  1. 哈希,考虑求斜率分母为0或者分子为0和浮点误差的情况 所以要存以分数的形式存储直线的斜率,根据数据范围设计好哈希函数,然后把哈希出来的数存到map里进行查找,具体思路在代码注释中。
#include<bits/stdc++.h>
#define IOS std::ios_base::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);// 快读

using namespace std;
typedef long long ll;
const ll inf = 4e9 + 1;
const int N  = 2E3 + 10;
struct ty{
    ll x,y;// x = (xa - x2) y = (y1 - y2) k = y/x; 考虑x为0 和y为0的特殊情况 所以分开存储
    ty (){};
    ty (ll a,ll b) : x(a),y(b) {};

    ty operator - (const ty & a) const{
        return ty{x - a.x,y - a.y};
    }
};

ll Hush(ty a){// 要保证斜率相同的直线哈希出来的结果也相同
    ll g = __gcd( abs(a.x), abs(a.y) );// 如果不约分 斜率实际相同,但是分子分母不同的会被哈希成不同键值
    a.x /= g;
    a.y /= g;
    if(a.x == 0)    a.y = 1;// 要特殊的判断 与y轴平行的直线斜率都是无穷大,但是如果y值不同实际哈希结果不同
    if(a.y == 0)    a.x = 1;// 同理
    if(a.x < 0)     a.x = -a.x, a.y = -a.y;// 同理

    // 哈希函数的设计一定要保证给定的数据范围不会冲突
    // -2e9 <= a.x <= 2e9 ,  2e9 <= a.y <= 2e9 
    // 设计思路 x,y两个关键字的影响因子要,不同,当其中一个不一样,另一个怎么变化,哈希值都不一样
    // 所以你可以把x的影响因子设计的很大,一旦x不同,y多大差距都不会出现哈希结果相同
    // 考虑y的范围, 可以给x的影响因子设计成3e9 这样久能保证定不会冲突了
    // long long 9e18  哈希值最大 2e9 * 4e9 + 2e9 = 8e18
    return a.x * inf + a.y; 
}

unordered_map<ll,int> mp;
unordered_map<ll,int> all_mp[N];
ty a[N];
ll k[N];

int main(){
    IOS
    int n,m;    cin >> n >> m;
    for(int i = 1; i <= n; ++i) cin >> a[i].x >> a[i].y;
    for(int i = 1; i <= n; ++i)
    for(int j = 1; j <= n; ++j) if(j != i)  ++all_mp[i][Hush(a[i] - a[j])];
    

    while(m--){

        long long ans = 0;
        ty t;   cin >> t.x >> t.y;
        for(int i = 1; i <= n; ++i) ++mp[Hush(a[i] - t)];

        // 这一步 很关键,因为Hush很耗时间,且 这个斜率的查找会用到两次,所以提前存储
        for(int i = 1; i <= n; ++i) k[i] = Hush({a[i].y - t.y, t.x - a[i].x});// 

        for(int i = 1; i <= n; ++i)// 直角在询问的点上
            if(mp.count(k[i]))  ans += mp[k[i]];
        
        ans >>= 1;// 上面答案会被重复记录一次

        for(int i = 1; i <= n; ++i) // 直角不在询问的点上
            if(all_mp[i].count(k[i]))   ans += all_mp[i][k[i]];
        
        cout << ans << '\n';
        mp.clear();
    }
    return 0;
}


  1. 不用哈希,直接用分数形式的斜率进行排序,然后二分查找指定斜率的直线个数,细节看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2E3 + 10;
struct ty{
    ll x,y;
    ty (){};
    ty (ll a,ll b) : x(a), y(b){};
    bool operator < (const ty &a)const {

        return y * a.x < a.y * x;
    }

    ty operator - (const ty &a) const{
        return ty{x - a.x, y - a.y};
    }
};

//调整斜率保证排序正确
void spj(ty & a){

    //如 0/3 2/3    0/-3 2/3 排序结果不同。但0/3 和 0/-3 斜率实际上是一样的
    if(a.x == 0) a.y = 1;
    //如 3/0 2/3    -3/0 2/3 排序结果不同。但3/0 和 -3/0 斜率实际上是一样的
    if(a.y == 0) a.x = 1;
     // 保证排序正确分母a.x一定不能为负数
     //如 -2/3 2/3    2/-3 2/3 排序结果不同。但-2/3 和 2/-3 斜率实际上是一样的
    if(a.x < 0) a.x = -a.x,a.y = -a.y;
}

ty a[N], mp[N],amp[N][N];

int main(){
    int n,m;    cin >> n >> m;
    for(int i = 1; i <= n; ++i)
        cin >> a[i].x >> a[i].y;
    for(int i = 1; i <= n; ++i){
        int cnt = 0;
        for(int j = 1; j <= n; ++j){
            if(i == j)  continue;
            ty p = a[i] - a[j];
            spj(p);
            amp[i][++cnt] = p;
        }
        sort(amp[i] + 1,amp[i] + n);
    }
    while(m--){
        long long ans = 0;
        ty t;   cin >> t.x >> t.y;
        for(int i = 1; i <= n; ++i){
            ty p = a[i] - t;
            spj(p);
            mp[i] = p;
        }

        sort(mp + 1,mp + n + 1);
        for(int i = 1; i <= n; ++i){
            ty p{a[i].y - t.y, t.x - a[i].x};// (y1 - y2)/(x1 - x2) * k= - 1;
            spj(p);
            ans += upper_bound(mp + 1,mp + n + 1,p) - lower_bound(mp + 1,mp + n + 1,p);
        }
        ans >>= 1;
        for(int i = 1; i <= n; ++i){
            ty p{a[i].y - t.y, t.x - a[i].x};
            spj(p);
            ans += upper_bound(amp[i] + 1,amp[i] + n,p) - lower_bound(amp[i] + 1,amp[i] + n ,p);
        }
        cout << ans << '\n';
    }
    return 0;

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值