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

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个点 ( x i , y i ) (x_i,y_i) (xi,yi),现在问你有多少个不同的集合 S S S,在这个集合中存在两点 u , v ( u ≠ v ) u,v(u\neq v) u,v(u̸=v),使得所有的 w ∋ S w\ni S wS满足下式
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 ) = ( x u − x v ) 2 + ( y u − y v ) 2 f(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 ) 2 g(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的情况,那么这三点必能构成一个三角形,假设 u v uv uv为最长边为 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 − a a&lt;L-a a<La
a &lt; L 2 a&lt;\frac{L}{2} a<2L
f ( u , v ) &lt; f ( u , v ) + f ( u , w ) + f ( w , v ) 2 f(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)
与原式不符所以所有非共线的集合肯定是不符合的
再考虑共线的情况,考虑 w w w u v uv uv外,假设 w w w v v 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)&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 − n 1-n 1n个点和这个点构成集合那么方法数就有
C n 1 + C n 2 + ⋯ + C n n = 2 n − C n 0 = 2 n − 1 C_n^1+C_n^2+\cdots+C_n^n=2^n-C_n^0=2^n-1 Cn1+Cn2++Cnn=2nCn0=2n1
若当前选择的这个点有m个重复点那么这m个点有两种贡献一种是这m个点自己之间所构成的集合为m中选 2 − m 2-m 2m,即
C m 2 + C m 3 + ⋯ + C m n = 2 m − m − 1 C_m^2+C_m^3+\cdots+C_m^n=2^m-m-1 Cm2+Cm3++Cmn=2mm1
另一方面从这 m m m个点中选出 1 − m 1-m 1m个点与这n个点组合即
( C m 1 + C m 2 + ⋯ + C m m ) ( C n 1 + C n 2 + ⋯ + C n n ) = ( 2 m − 1 ) ( 2 n − 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个点,避免重复计数
对于如何统计共线的点的个数呢,对于每次选取的 i i i点,我们遍历 i i i后面的点即为 j j j,由于直线的有一端已经固定为 i i i了那么对于 j j j来说只要记录 i j ij ij的斜率就可以知道有多少个点与 i i i共线了,如果斜率用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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值