题意:
在平面坐标系上给定n个不同的整点,我们称从这n个点中选择6个不同的点所组成的有序六元组(A,B,C,D,E,F)是一条“鱼”,当且仅当:
- $AB=AC,BD=CD,DE=DFAB=AC,BD=CD,DE=DF$(身形要对称)
- $angle BAD,angle BDA,angle CAD,angle CDA<90^circ$(脑袋和屁股显然不能是凹的)
- $angle ADE,angle ADF>90^circ$
求这n个点能组成多少条鱼。点集相同而顺序不同的算多种方案。
$nleq 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)})$。注意特判同一条直线正负两边的情况。
维护中点时也尽量用整数,直接将坐标和$ imes 2$就行了。
然后将线段BC排序,第一关键字是斜率,然后如果两个BC对应的直线AD相同(即这两个BC中点的连线垂直于它们自己,可以点积判)就按中点坐标大小排序,否则按它们对应AD的顺序排序(正反无所谓)。
当然也可以将每个BC对应的AD用斜率和截距表示出来并用map离散化。不过我这样写完调不出来了。
然后每次只需要$lowerbound$一下就可以算答案了。
复杂度$O(n^{2}log{n})$,细节巨多。我考场上必拿0分。
套路:
- 降低枚举复杂度:考虑哪些是必须枚举的,哪些是枚举完必须枚举的之后可以预处理的。
- 写计算几何时:思路清晰优先于代码简洁。
代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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 ",ans); return 0; }