Eureka
Time Limit: 8000/4000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)Total Submission(s): 824 Accepted Submission(s): 207
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 .
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.
3 3 1 1 1 1 1 1 3 0 0 0 1 1 0 1 0 0
4 3 0
分析题目,n个点,定义 f(u,v)=(xu−xv)2+(yu−yv)2‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾√ 即为u,v两点距离
g(u,v,w)=f(u,v)+f(v,w)+f(w,u)2 .
要求找一些点集,满足每个集合中,存在至少一个点对(u,v),让集合中任何一点w,满足g(u,v,w) >= f(u,v)
化简为 f(u,v) >= f(v,w)+f(w,u) ,可知大于号是无法满足的,即w在u,v线段上。
这样要找的集合其实是一条线段,点对u,v其实就是线段两个端点。
要求找出所有集合,当找到一条无法再延长的线段时,线段上有c个点,其实这条线段上所有的集合数目就是C(c,2)+C(c,3)+C(c,4)+....+C(c,c)
有没有很眼熟。其实就是C(c,0)+..+C(c,c) - C(c,0) - C(c,1) = 2^c - 1 - c
可以提前处理出来,因为max(c) <= n
对于找线段,可以n^2 log(n)处理
n^2是枚举点对,
i 0 -> n
j i+1->n
找出以i点为原点的不同方向上的点的个数,方向可以用向量存储。
方向上点的个数用map存
遍历完一个i之后,map中存的其实就是包含该点的线段,当然,之前统计过的就不会再统计了。
为了放置出现多次统计,计算组合的时候,2^cnt - 1 - cnt cnt表示该向量上 不包含i点 的点的个数。这样其实就是默认集合中有i
最后记得处理重点,如果单纯按上面处理重点也没有影响。
优化的话,提前对点排个序,然后用一个数组统计重点个数,用i遍历点的时候,累计进去i点及其重点的组合
代码如下:
#include <iostream>
#include <cmath>
#include <vector>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <queue>
#include <stack>
#include <list>
#include <algorithm>
#include <map>
#include <set>
#define LL long long
#define Pr pair<int,int>
#define fread(ch) freopen(ch,"r",stdin)
#define fwrite(ch) freopen(ch,"w",stdout)
using namespace std;
const int INF = 0x3f3f3f3f;
const int msz = 10000;
const int mod = 1e9+7;
const double eps = 1e-8;
struct Point
{
LL x,y;
Point(){}
Point(LL _x,LL _y):x(_x),y(_y){}
bool operator <(const struct Point a)const
{
return x == a.x? y < a.y: x < a.x;
}
};
map <Point,int> mp;
map <Point,int>::iterator iter;
Point nd[1111];
LL mult[1111];
int cnt[1111];
int main()
{
//fread("");
//fwrite("");
int t;
int n;
scanf("%d",&t);
//预处理2^n
mult[0] = 1;
for(int i = 1; i <= 1000; ++i)
mult[i] = (mult[i-1]<<1)%mod;
while(t--)
{
scanf("%d",&n);
bool f = 1;
for(int i = 0; i < n; ++i)
{
scanf("%lld%lld",&nd[i].x,&nd[i].y);
if(i && (nd[i].x != nd[i-1].x || nd[i].y != nd[i-1].y)) f = 0;
}
//全是一个点 直接输出组合
if(f)
{
printf("%lld\n",mult[n]-1-n);
continue;
}
sort(nd,nd+n);
memset(cnt,0,sizeof(cnt));
//压缩重点
int tp = 0;
for(int i = 0; i < n; ++i)
{
if(i == 0 || nd[i].x != nd[i-1].x || nd[i].y != nd[i-1].y)
{
cnt[tp] = 1;
nd[tp++] = nd[i];
}
else cnt[tp-1]++;
}
n = tp;
LL x,y;
LL g;
LL ans = 0;
for(int i = 0; i < n; ++i)
{
//printf("%lld %lld:\n",nd[i].x,nd[i].y);
mp.clear();
for(int j = i+1; j < n; ++j)
{
x = nd[i].x - nd[j].x;
y = nd[i].y - nd[j].y;
//掰正向量
if(x < 0)
{
x = -x;
y = -y;
}
g = __gcd(x,y);
//平行 垂直 简化
if(x == 0) mp[Point(0,1)] += cnt[j];
else if(y == 0) mp[Point(1,0)] += cnt[j];
else
{
x /= g;
y /= g;
mp[Point(x,y)] += cnt[j];
}
}
for(iter = mp.begin(); iter != mp.end(); ++iter)
{
ans += ((mult[cnt[i]]-1)*(mult[iter->second]-1))%mod;
//printf("%lld %lld %lld\n",iter->first.x,iter->first.y,mult[cnt[i]+iter->second]-1-cnt[i]-iter->second);
}
//重点组合
ans = (ans+mult[cnt[i]]-1-cnt[i])%mod;
}
printf("%lld\n",ans);
}
return 0;
}