【HDOJ 5738】Eureka(向量哈希+组合数学)

【HDOJ 5738】Eureka(向量哈希+组合数学)

Eureka

Time Limit: 8000/4000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 824    Accepted Submission(s): 207


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,vP,uv)  are called best pair, if for every  wP f(u,v)g(u,v,w) , where  f(u,v)=(xuxv)2+(yuyv)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   (1n1000)  -- then number of points.

Each of the following  n  lines contains two integers  xi  and  yi   (109xi,yi109)  -- 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
 

Author
zimpha
 

Source
 

Recommend
wange2014   |   We have carefully selected several similar problems for you:   5746  5744  5743  5741  5740 


分析题目,n个点,定义  f(u,v)=(xuxv)2+(yuyv)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;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值