HDU 5738 Eureka (from: 2016 Multi-University Training Contest 2)


题意:给出n(1<=n<=1000)个平面上的点,问n个点中存在多少符合如下条件的点集:点集中至少存在两点,这两点的距离永远大于点集中任意一点和这两个点组成环的长度的一半。

思路:
首先平面几何大家一定很熟悉了,应该可以很快得出以下结论:
1.所处位置相同的点组成的点集符合条件
2.所处位置都在同一条直线上的点组成的点集符合条件
简而言之,不存在三角形的点集符合条件。
思考的时候可以从这三种情况讨论:
1.从相同位置的点中选出任意个点组成点集
2.从分别处于两个不同位置的几个点中选出任意个点组成点集
3.从分别处于两个不同位置以及在这两个位置组成线段上的几个点中组成点集

n最大只有1000,时限也是4s,此时可以判断O(n^2)的时间复杂度可以解决问题。
接下来要给点进行重编号和排序。
下面两段是重编号和排序的理由,读者自己选择看不看吧, 可直接跳过
重编号:便于用数组储存各个点的信息,map可以存点的信息,但是map对于一个key值的查找要logn的时间,这一点会大大增加时间消耗,重编号后用数组存,可以用O(1)的时间拿到需要的信息,且空间也更节省。(安神告诉我unordermap是根据hash值来储存的,在hash方法合理,数据不卡的情况下,也可以实现接近O(1)的查找)
排序:点重编号后,只要枚举两两个点,和其所在直线的情况就ok了,不过要考虑的依然很多,首先是重复计算,暴力的n^2遍历会把每个点算上两次,处在这两点直线上的点我们更是不得而知。其次计算一个线段上的点组成的点集时,我们只能取线段上的点,线段外的点不应计算(不然还是会重复计算)。因此需要给题目中的点排序。

自己使用了multiset来同时完成这两件事,set内部是有序的,且multiset允许相同的元素存在,便于计数。(PS:stl对两个对象是否相等的判断是通过小于运算符或一个有调用运算符的对象实现的……有点难理解吧,其实自己很早以前就写了一篇叫《用stl优化贪心问题》的博文,只不过感觉要讲得东西太多,写了一半不想写了,放在了草稿箱= =)

反正大题思路就是这样了,一些具体操作再讲一下。(详见代码)
计数:当然是组合数了,一个位置拿出几个点,另一个位置拿出几个点,是线段的话还要考虑线段中的点拿出几个点。这几种情况都记录进答案就好了(其实这里还是很容易出错的,要仔细)。
n^2遍历:点已经是有序的了,因此枚举第i个点的时候不需要遍历所有点,只要遍历位置在第i点后面的点就可以避免重复计算。

几个点是否在同一条直线上可以通过几个点和一个固定点的斜率是否相同来判断,由于前面用set时就重载了结构体的小于运算符,可以用储存点的结构体以分数形式保存斜率。所以每枚举一个点时,就用清空一个map,接着用map来记录后面斜率的出现次数。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<set>
#include<map>
using namespace std;
#define MS(x,y) memset(x,y,sizeof(x))
typedef long long LL;
const int MAXN=1e3+5;
const int MOD=1e9+7;
struct Node{
    LL x,y;
    bool operator < (const Node& rhs)const{
        return x<rhs.x||(x==rhs.x&&y<rhs.y);
    }
}node[MAXN],now,pre,K;
multiset<Node> S;
multiset<Node>::iterator it;
map<Node,int> mp;
int tot;
LL C[MAXN][MAXN],ret[MAXN];
int cnt[MAXN];

Node calu(int a,int b){
    LL x=node[b].x-node[a].x;
    LL y=node[b].y-node[a].y;
    if(x==0&&y==0) return (Node){0,0};
    if(x==0) return (Node){0,1};
    if(y==0) return (Node){1,0};
    LL gcd;
    if(y>0){
        gcd=__gcd(x,y);
        return (Node){x/gcd,y/gcd};
    }
    if(y<0){
        y=-y;
        gcd=__gcd(x,y);
        return (Node){-x/gcd,y/gcd};
    }
}

int main(){
    MS(ret,0);
    C[0][0]=1;
    for(int i=1;i<MAXN;++i){
        C[i][0]=C[i][i]=1;
        ret[i]=1;
        for(int j=1;j<i;++j){
            C[i][j]=(C[i-1][j]+C[i-1][j-1])%MOD;
            ret[i]=(ret[i]+C[i][j])%MOD;
        }
    }
    int T;
    scanf("%d",&T);
    while(T--){
        LL ans=0,x,y;
        tot=0;
        S.clear();
        MS(cnt,0);
        int n;
        scanf("%d",&n);
        for(int i=0;i<n;++i){
            scanf("%I64d%I64d",&x,&y);
            S.insert((Node){x,y});
        }
        now=*(S.begin());
        node[++tot]=now;
        cnt[tot]++;
        pre=now;
        it=S.begin();
        ++it;
        for(;it!=S.end();++it){
            now=(*it);
            if(pre.x==now.x&&pre.y==now.y){
                ++cnt[tot];
                continue;
            }
            node[++tot]=now;
            cnt[tot]++;
            pre=now;
        }
        for(int i=1;i<=tot;++i){
            ans=(ans+ret[cnt[i]]-cnt[i])%MOD;
            mp.clear();
            for(int j=i+1;j<=tot;++j){
                ans=(ans+ret[cnt[i]]*ret[cnt[j]]%MOD)%MOD;
                K=calu(i,j);
                int kcnt=mp[K];
                if(kcnt>0){
                    ans=(ans+ret[cnt[i]]*ret[cnt[j]]%MOD*ret[kcnt]%MOD)%MOD;
                }
                mp[K]+=cnt[j];
            }
        }
        printf("%I64d\n",ans);
    }
}

觉得自己讲的不是很好,如果读者有疑问,欢迎指出来。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值