2016暑假多校联赛 HDU5738

官方题解:

xjb推导一下可以知道best set一定是一些共线的点, 于是问题变成问有多少个子集共线. 首先, 把所有点按照(x,y)(x,y)(x,y)双关键字排序, 然后枚举最左边的点iii, 那么其他点jjj一定满足j>ij > ij>i. 把在这个点右边的点都做下极角排序(按照1gcd(dx,dy)(dx,dy)\frac{1}{gcd(dx, dy)}(dx, dy)gcd(dx,dy)1(dx,dy)排序), 统计下共线的就好了. 需要注意下对重点的处理.

不会极角排序,但也能做。

另外大神题解:

公式很有用。

这道题前面给了两个公式,其实仔细分析一下,就会发现其实是给了你一堆点的坐标,然后让你求这些点有多少种组合可以形成共线的情况

当两个点在一个坐标上时这两个点可以看做是不同的两个点,也就是说如果两个点在一个坐标上,这两个点也算共线

我的这道题的解题思路就是先把每一个点的横坐标按从小到大排好序,然后从第一个点开始遍历,当遍历到一个点的时候,以这个点为起点,再向下遍历,

记录这个起点和以后每个点的向量,把这个向量化简到最小,用一个map保存起来

最后通过总共有多少个向量数来计算有多少种共线

我们可以先总结出一个规律,当有n个点共线时,组合个数为:2^n-n-1

由于我们在第一次遍历中已经确定了起点,所以每一次只会有1中多点在同一个坐标上的情况

向量分为两种

第一种是:(0,0)这样的向量代表有多个点是在一个坐标上,此时我们就要先计算出在一个点上有多少个情况共线

这样的向量有n个,就代表着有n+1个点,通过公式我们可以计算出多个点在一个坐标上时的组合个数,通过上面的公式:2^(n+1)-n-2

第二种是:向量中都不为0,此时组合的个数为:前面得到的在一个坐标上点的个数的组合数乘上向量个数的组合数,前面的在一个坐标上的数量为n1,当前向量个数为n2

通过公式可以得到:(2^n1-1)*(2^(n2+1)-1)

把这两种加起来就是以一个点为起点时的共线的组合个数,最后遍历全部再相加就是总的数量了

注意:本题要用long long否则无法通过!!!!!!!!!!!!!!!!!!!!

AC代码:

#include<stdio.h>
#include<iostream>
#include<math.h>
#include<stdlib.h> 
#include<string.h>
#include<algorithm>
#include<stack>
#include<vector>
#include<queue>
#include<map>
#include<iterator>
#include<math.h>
#include<set>
using namespace std;
#define MAX 1000000007
long long gcd(long long x,long long y) //最大公约数的求值
{  
    if(y==0) return x;  
    else return(gcd(y,x%y));  
}   
long long mod_exp(long long a, long long b,long long c) //最大幂取余的模板,a^b%c
{
    long long res, t;
    res = 1 % c; 
    t = a % c;
    while (b)
    {
        if (b & 1)
        {
            res = res * t % c;
        }
        t = t * t % c;
        b /= 2 ;
    }
    return res;
}
struct Point //这是点的结构体,也是向量的结构体,其中需要对<进行重载,否则map无法通过编译
{
	long long x;long long y;
	bool operator<(const Point&x)const{
		if(this->x==x.x)
			return this->y<x.y;
		else
			return this->x<x.x;
	}
}pp[1100];
map<Point,long long> f; //这个是记录不同向量的个数的
map<Point,long long>::iterator it;//迭代器
map<Point,long long>g;//对在同一坐标上的点进行标记
bool cmp(Point x,Point y) 
{
	return x<y;

}
int main()
{
	long long t,n,sum,ans,index,i,j;
	scanf("%lld",&t);
	while(t--)
	{
		g.clear();
		f.clear();
		ans=0;
		scanf("%lld",&n);
		for(i=0;i<n;i++)
		{
			scanf("%lld %lld",&pp[i].x,&pp[i].y);
		}
		sort(pp,pp+n); //对每一个点进行排序
		for(i=0;i<n;i++)
		{
			
			if(g[pp[i]]) //如果当前的是以前判定过的在一个坐标上的点,则跳过这个点,因为已经计算过了
				continue;
			f.clear(); //清空map
			for(j=i+1;j<n;j++)
			{
				long long x=pp[j].x-pp[i].x;//得到向量
				long long y=pp[j].y-pp[i].y;
				if(x<0)//把向量调正
				{
					x=-x;
					y=-y;
				}
				long long GCD=gcd(x,abs(y));
				if(x!=0&&y!=0)//这个是如果向量的x或向量的y是0的话直接把另一个向量化为1
				{
					if(x==0)
						y=1;
					if(y==0)
						x=1;
				}
				if(GCD!=0) //把向量化简到x和y为互质的程度
				{
					x/=GCD;
					y/=GCD;
				}
				Point temp;
				temp.x=x;
				temp.y=y;
				f[temp]++; //记录向量的个数
			}
			long long k;
			Point ggg; //这个是在坐标一个点上的向量,因为在一个点上,所以他的x和y都为0
			ggg.x=0;
			ggg.y=0;
			long long temp;
			temp=f[ggg]; //temp为以当前点为起点在一个坐标上的向量的个数
			for(it=f.begin();it!=f.end();it++) //开始遍历map
			{
				if(it->first.x==0&&it->first.y==0) //如果是在一个点的向量,就是向量为(0,0),那么直接就算这些点有多少种组合
				{
					k=mod_exp(2,it->second+1,MAX);
					k=k-it->second-2;
					ans+=k;		
					ans%=MAX;
				}
				else //如果不是,则要计算组合的乘积
				{
					k=mod_exp(2,it->second,MAX);
					k=k-1;//这个k是在一个坐标上的点有多少种组合
					ans+=(k*(mod_exp(2,temp+1,MAX)-1));//然后把k和后面的点有多少种组合进行相乘
					ans%=MAX;
				}
			}
			g[pp[i]]=1;//把在一个坐标的点上进行标记,以后就不再看了
		}
		ans%=MAX;
		printf("%lld\n",ans);
	}
    return 0;
} 


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值