HDU 5738 Eureka(统计共线点数+组合数)

本文深入解析Eureka算法,探讨在平面坐标系中寻找特定点集的数学原理与编程实现。通过计算点间距离和评估点集特性,文章揭示了如何判断点集是否满足特定条件,重点在于理解并应用几何与组合数学概念。

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

Eureka
Time Limit: 8000/4000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 4007 Accepted Submission(s): 1096

Problem Description
Professor Zhang draws n points on the plane, which are conveniently labeled by 1,2,…,n. The i-th point is at (xi,yi). Professor Zhang wants to know the number of best sets. As the value could be very large, print it modulo 109+7.

A set P (P contains the label of the points) is called best set if and only if there are at least one best pair in P. Two numbers u and v (u,v∈P,u≠v) are called best pair, if for every w∈P, f(u,v)≥g(u,v,w), where f(u,v)=(xu−xv)2+(yu−yv)2−−−−−−−−−−−−−−−−−−−√ and g(u,v,w)=f(u,v)+f(v,w)+f(w,u)2.

Input
There are multiple test cases. The first line of input contains an integer T, indicating the number of test cases. For each test case:

The first line contains an integer n (1≤n≤1000) – then number of points.

Each of the following n lines contains two integers xi and yi (−109≤xi,yi≤109) – coordinates of the i-th point.

Output
For each test case, output an integer denoting the answer.

Sample Input
3
3
1 1
1 1
1 1
3
0 0
0 1
1 0
1
0 0

Sample Output
4
3
0

题意

给你n个点(xi,yi)(x_i,y_i)(xi,yi),现在问你有多少个不同的集合SSS,在这个集合中存在两点u,v(u≠v)u,v(u\neq v)u,v(u̸=v),使得所有的w∋Sw\ni SwS满足下式
f(u,v)≥g(u,v,w)f(u,v)\geq g(u,v,w)f(u,v)g(u,v,w)
其中f(u,v)=(xu−xv)2+(yu−yv)2f(u,v)=\sqrt{(x_u-x_v)^2+(y_u-y_v)^2}f(u,v)=(xuxv)2+(yuyv)2
g(u,v,w)=f(u,v)+f(u,w)+f(w,v)2g(u,v,w)=\frac{f(u,v)+f(u,w)+f(w,v)}{2}g(u,v,w)=2f(u,v)+f(u,w)+f(w,v)

思路

f(x,y)f(x,y)f(x,y)就是两点之间的距离
若我们已经确定了两点,那么第三个点应该在哪呢,首先考虑wu,v的情况,那么这三点必能构成一个三角形,假设uvuvuv为最长边为a=f(u,v)a=f(u,v)a=f(u,v),总周长为L=f(u,v)+f(u,w)+f(w,v)L=f(u,v)+f(u,w)+f(w,v)L=f(u,v)+f(u,w)+f(w,v)
那么有L−a=f(u,w)+f(w,v)L-a=f(u,w)+f(w,v)La=f(u,w)+f(w,v)对于三角形的最长边来说两边之和一定大于最长边即
a&lt;L−aa&lt;L-aa<La
a&lt;L2a&lt;\frac{L}{2}a<2L
f(u,v)&lt;f(u,v)+f(u,w)+f(w,v)2f(u,v)&lt;\frac{f(u,v)+f(u,w)+f(w,v)}{2}f(u,v)<2f(u,v)+f(u,w)+f(w,v)
与原式不符所以所有非共线的集合肯定是不符合的
再考虑共线的情况,考虑wwwuvuvuv外,假设wwwvvv的外面那么有
g(u,v,w)=f(u,v)+f(w,v)&gt;f(u,v)g(u,v,w)=f(u,v)+f(w,v)&gt;f(u,v)g(u,v,w)=f(u,v)+f(w,v)>f(u,v)
所以这种情况也肯定是不行的,那么就是wuv内部包括端点
f(u,w)+f(w,v)=f(u,v)f(u,w)+f(w,v)=f(u,v)f(u,w)+f(w,v)=f(u,v)

f(u,v)=g(u,v,w)=f(u,v)f(u,v)=g(u,v,w)=f(u,v)f(u,v)=g(u,v,w)=f(u,v)
那么题意就转化为共线的点集合有多少了,实际上我们找到一个共线的集合,必有两点在最两端,其他点都在这两点之内的,所以目标就是求共线点的集合数了
首先我们要对所有的点按x最小,若x相等按y小排个序,这样之后我们能保证我们遍历点的时候,第一个点总是与他共线的所有点里面的最左端的点
在这里插入图片描述
对于某一个点来说,若不包括这个点有n个点与其共线,可以从这n个点里面选1−n1-n1n个点和这个点构成集合那么方法数就有
Cn1+Cn2+⋯+Cnn=2n−Cn0=2n−1C_n^1+C_n^2+\cdots+C_n^n=2^n-C_n^0=2^n-1Cn1+Cn2++Cnn=2nCn0=2n1
若当前选择的这个点有m个重复点那么这m个点有两种贡献一种是这m个点自己之间所构成的集合为m中选2−m2-m2m,即
Cm2+Cm3+⋯+Cmn=2m−m−1C_m^2+C_m^3+\cdots+C_m^n=2^m-m-1Cm2+Cm3++Cmn=2mm1
另一方面从这mmm个点中选出1−m1-m1m个点与这n个点组合即
(Cm1+Cm2+⋯+Cmm)(Cn1+Cn2+⋯+Cnn)=(2m−1)(2n−1)(C_m^1+C_m^2+\cdots+C_m^m)(C_n^1+C_n^2+\cdots+C_n^n)=(2^m-1)(2^n-1)(Cm1+Cm2++Cmm)(Cn1+Cn2++Cnn)=(2m1)(2n1)
由于经过了排序所以重复的点的下标是连续的所以在每次求完m个点的贡献后要直接跳过m个点,避免重复计数
对于如何统计共线的点的个数呢,对于每次选取的iii点,我们遍历iii后面的点即为jjj,由于直线的有一端已经固定为iii了那么对于jjj来说只要记录ijijij的斜率就可以知道有多少个点与iii共线了,如果斜率用double存的话会有精度的问题所以可以用map中套pair的方法来记录即

map<pair<int,int>,int>
#include<bits/stdc++.h>
using namespace std;
#define X first
#define Y second
const int MAXN=1000+5;
const int mod=1e9+7;
int n;
pair<int,int>node[MAXN];
map<pair<int,int>,int>mp;
long long quickmod(long long a,long long b)
{
    long long ans=1;
    while(b)
    {
        if(b%2==1)
            ans=ans*a%mod;
        a=a*a%mod;
        b=b/2;
    }
    return ans;
}
signed main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        for(int i=0; i<n; ++i)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            node[i]=make_pair(u,v);
        }
        sort(node,node+n);
        long long ans=0;
        for(int i=0; i<n;)
        {
            long long res=1;
            mp.clear();
            for(int j=i+1; j<n;j++)
            {
                int x0=node[i].X,y0=node[i].Y,x1=node[j].X,y1=node[j].Y;
                if(x0==x1&&y0==y1)
                {
                    res++;
                    continue;
                }
                int a=y1-y0,b=x1-x0;//c=-b*y0-a*x0
                int gcd=__gcd(a,b);
                a/=gcd,b/=gcd;
                mp[make_pair(a,b)]++;
            }
            if(res>1)
            {
                ans=(ans+quickmod(2,res)-res-1+mod)%mod;
            }
            for(map<pair<int,int>,int>::iterator j=mp.begin(); j!=mp.end(); j++)
            {
                ans=(ans+(quickmod(2,res)-1+mod)%mod*(quickmod(2,j->Y)-1+mod)%mod)%mod;
            }
            i+=res;
        }
        printf("%lld\n",ans);
    }
    return 0;
}

//(y1-y0)X+(x0-x1)Y+(x1y0-x0y1)=0

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值