[HNOI2019] 鱼

题意:

在平面坐标系上给定n个不同的整点,我们称从这n个点中选择6个不同的点所组成的有序六元组(A,B,C,D,E,F)是一条“鱼”,当且仅当:

  1. $AB=AC,BD=CD,DE=DFAB=AC,BD=CD,DE=DF$(身形要对称)
  2. $\angle BAD,\angle BDA,\angle CAD,\angle CDA<90^\circ$(脑袋和屁股显然不能是凹的)
  3. $\angle ADE,\angle ADF>90^\circ$

求这n个点能组成多少条鱼。点集相同而顺序不同的算多种方案。

$n\leq 1000$。

 

题解:

首先发现一个鱼的核心就是线段AD,在确定AD后,BC与EF互不影响。

所以我们考虑$O(n^{2})$枚举AD,然后预处理出BC与EF的贡献。

EF看起来比较容易,我们只需要将某个半平面内的点按dis分类并维护组合数,可以直接支持单点修改。

先极角排序,合法的点一定是一段区间,我们考虑维护双指针$l$和$r$,并将序列延长一倍以避免跳出边界(由于按极角序枚举A,一定不会跳出2n)。

以D为原点,DA为x轴正方向建系,那么每次$r$要跑到第四象限的第一个点,$l$要跑到第二象限的第一个点。用叉积和点积判一下即可。

BC看起来比较麻烦,需要满足BC垂直于AD且BC的中点在AD上。那么考虑预处理所有线段BC并维护这两个东西。

维护斜率时尽量用整数,只需要将$(x,y)$处理成$(\frac{x}{gcd(x,y)},\frac{y}{gcd(x,y)})$。注意特判同一条直线正负两边的情况。

维护中点时也尽量用整数,直接将坐标和$\times 2$就行了。

然后将线段BC排序,第一关键字是斜率,然后如果两个BC对应的直线AD相同(即这两个BC中点的连线垂直于它们自己,可以点积判)就按中点坐标大小排序,否则按它们对应AD的顺序排序(正反无所谓)。

当然也可以将每个BC对应的AD用斜率和截距表示出来并用map离散化。不过我这样写完调不出来了。

然后每次只需要$lowerbound$一下就可以算答案了。

复杂度$O(n^{2}\log{n})$,细节巨多。我考场上必拿0分。

 

套路:

  • 降低枚举复杂度:考虑哪些是必须枚举的,哪些是枚举完必须枚举的之后可以预处理的。
  • 写计算几何时:思路清晰优先于代码简洁。

 

代码:

#include<bits/stdc++.h>
#define maxn 1000005
#define maxm 500005
#define inf 0x7fffffff
#define ll long long
#define rint register ll
#define debug(x) cerr<<#x<<": "<<x<<endl
#define fgx cerr<<"--------------"<<endl
#define dgx cerr<<"=============="<<endl

using namespace std;
map<ll,ll> siz;

struct point{
    ll x,y;
    point operator+(const point b)const{return (point){x+b.x,y+b.y};}
    point operator-(const point b)const{return (point){x-b.x,y-b.y};}
    ll operator*(const point b)const{return x*b.y-y*b.x;}
    bool operator<(const point b)const{return x==b.x?y<b.y:x<b.x;}
}P[maxn],tp[maxn];
inline ll dot(point a,point b){return a.x*b.x+a.y*b.y;}
struct line{
    point dir,mid;
    bool operator<(const line b)const{
        if(dir.x!=b.dir.x || dir.y!=b.dir.y) return dir<b.dir;
        else if(dot(dir,mid)!=dot(b.dir,b.mid)) return dot(dir,mid)<dot(b.dir,b.mid);
        else return mid<b.mid;
    }
}L[maxn];

inline ll read(){
    ll x=0,f=1; char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    return x*f;
}

inline ll dis(point a){return a.x*a.x+a.y*a.y;}
inline bool xx(point a){return (a.y==0)?(a.x<0):(a.y>0);}
inline bool cmp(point a,point b){return (xx(a)!=xx(b))?(xx(a)<xx(b)):(a*b>0);}
inline ll gcd(ll a,ll b){return b==0?a:gcd(b,a%b);}
inline point solve(point a){
    ll k=gcd(a.x,a.y); a.x/=k,a.y/=k;
    if(a.x<0) a.x=-a.x,a.y=-a.y;
    if(a.x==0) a.y=abs(a.y);
    return a;
}

int main(){
    ll n=read(),tot=0,ans=0;
    for(ll i=1;i<=n;i++) 
        P[i].x=read(),P[i].y=read();
    for(ll i=1;i<=n;i++)
        for(ll j=i+1;j<=n;j++){
            point k=solve(P[i]-P[j]);
            L[++tot]=(line){k,P[i]+P[j]};
        }
    sort(L+1,L+1+tot);
    for(ll i=1;i<=n;i++){
        point D=P[i]; siz.clear();
        ll l=1,r=1,cnt=0,num=0;
        for(ll j=1;j<=n;j++) 
            if(j!=i) tp[++cnt]=P[j]-D;
        sort(tp+1,tp+1+cnt,cmp);
        for(ll j=cnt+1;j<=cnt*2;j++) tp[j]=tp[j-cnt];
        for(ll j=1;j<=cnt;j++){
            point A=tp[j];
            while(dot(A,tp[r])<0 || A*tp[r]>0 || (A*tp[r]==0 && r<=cnt)) num+=(siz[dis(tp[r++])]++);
            while(l<r && dot(A,tp[l])>=0) num-=(--siz[dis(tp[l++])]);
            if(num){
                point k=solve((point){-A.y,A.x});
                point lp=D+D,rp=D+D+A+A;
                if(rp<lp) swap(lp,rp);
                ll s1=lower_bound(L+1,L+1+tot,(line){k,rp})-L;
                ll s2=upper_bound(L+1,L+1+tot,(line){k,lp})-L;
                ans+=4ll*(s1-s2)*num;
            }
        }
    }
    printf("%lld\n",ans);
    return 0;
}

 

posted @ 2020-06-17 20:37  Fugtemypt  阅读(304)  评论(0编辑  收藏  举报