CQOI2017 小Q的草稿
小Q的草稿
小 Q 是个程序员。
众所周知,程序员在写程序的时候经常需要草稿纸。小 Q 现在需要一张草稿纸用来画图,但是桌上只有一张草稿纸,而且是一张被用过很多次的草稿纸。
草稿纸可以看作一个二维平面,小 Q 甚至已经给它建立了直角坐标系。以前每一次草稿使用过的区域,都可以近似的看作一个平面上的一个三角形,这个三角形区域的内部和边界都不能再使用。当然了,以前的草稿也没有出现区域重叠的情况。
小 Q 已经在草稿纸上画上了一些关键点,这些关键点都在没使用过的区域。小 Q 想把这些关键点两两之间尽可能的用线段连接起来。连接两个关键点的线段有可能会穿过已经用过的草稿区域,这样显然不允许。于是小 Q 就想知道,有多少对关键点可以被线段连接起来,而且还不会穿过已经用过的区域。为了方便,小 Q 保证任意三个关键点不会共线。
对于 \(100\%\) 的测试点,\(1\le V,T\le 1000,0 \leq x_i,y_i \leq 10^8\),且都是整数。
题解
首先有显然的\(O(n^2m)\)暴力:枚举两个点判断有没有三角形与线段相交。
然后发现这个判断的过程可以加速。
我们可以枚举其中一个点\(i\),然后对\(j> i\)的点和三角形极角排序+扫描线。
显然对于某个角度范围,我们只需要判断离\(i\)点最近的那条线段是否遮挡了点\(j\)。这个可以用set维护,因为类似圆扫描线,这道题里三角形不相交,所以随着角度的变化,线段的相对顺序不会改变。
注意扫描线的顺序:先加边,在问点,后删边。
小优化:一个三角形只有一条边会有用,就是那条覆盖角度范围最大的那条边。
时间复杂度\(O(n^2\log n)\)。
CO double pi=acos(-1);
struct point {double x,y;};
IN point operator+(CO point&a,CO point&b){
return (point){a.x+b.x,a.y+b.y};
}
IN point operator-(CO point&a,CO point&b){
return (point){a.x-b.x,a.y-b.y};
}
IN point operator*(CO point&a,double b){
return (point){a.x*b,a.y*b};
}
IN point operator/(CO point&a,double b){
return (point){a.x/b,a.y/b};
}
IN double cross(CO point&a,CO point&b){
return a.x*b.y-a.y*b.x;
}
IN double dot(CO point&a,CO point&b){
return a.x*b.x+a.y*b.y;
}
IN double angle(CO point&a){
return atan2(a.y,a.x);
}
IN double len(CO point&a){
return sqrt(a.x*a.x+a.y*a.y);
}
IN point intersect(CO point&a,CO point&u,CO point&b,CO point&v){
return b+v*cross(b-a,u)/cross(u,v);
}
CO int N=1e4+10;
point p[N],q[N][3];
struct event{
double w;
int o;
point a,b;
}e[5*N];
IN bool operator<(CO event&a,CO event&b){
return a.w!=b.w?a.w<b.w:a.o>b.o;
}
struct seg {point a,b;};
double w;
IN bool operator<(CO seg&a,CO seg&b){
return len(intersect((point){0,0},(point){cos(w),sin(w)},a.a,a.b-a.a))<
len(intersect((point){0,0},(point){cos(w),sin(w)},b.a,b.b-b.a));
}
set<seg> h;
int main(){
int n=read<int>(),m=read<int>();
for(int i=1;i<=n;++i) read(p[i].x),read(p[i].y);
for(int i=1;i<=m;++i)for(int j=0;j<=2;++j) read(q[i][j].x),read(q[i][j].y);
int64 ans=0;
for(int i=1;i<=n;++i){
int tot=0;
for(int j=i+1;j<=n;++j)
e[++tot]=(event){angle(p[j]-p[i]),0,p[j]-p[i]};
for(int j=1;j<=m;++j){
pair<double,int> id(0,-1);
for(int k=0;k<=2;++k){
double l=angle(q[j][k]-p[i]),r=angle(q[j][(k+1)%3]-p[i]);
if(l>r) swap(l,r);
double w=r-l;
if(w>pi) w=2*pi-w;
id=max(id,make_pair(w,k));
}
int k=id.second;
double l=angle(q[j][k]-p[i]),r=angle(q[j][(k+1)%3]-p[i]);
if(l>r) swap(l,r);
double w=r-l;
if(w<pi){
e[++tot]=(event){l,1,q[j][k]-p[i],q[j][(k+1)%3]-p[i]};
e[++tot]=(event){r,-1,q[j][k]-p[i],q[j][(k+1)%3]-p[i]};
}
else{
e[++tot]=(event){-pi,1,q[j][k]-p[i],q[j][(k+1)%3]-p[i]};
e[++tot]=(event){l,-1,q[j][k]-p[i],q[j][(k+1)%3]-p[i]};
e[++tot]=(event){r,1,q[j][k]-p[i],q[j][(k+1)%3]-p[i]};
e[++tot]=(event){pi,-1,q[j][k]-p[i],q[j][(k+1)%3]-p[i]};
}
}
sort(e+1,e+tot+1);
for(int j=1;j<=tot;++j){
w=e[j].w;
if(e[j].o==1) h.insert((seg){e[j].a,e[j].b});
else if(e[j].o==-1) h.erase((seg){e[j].a,e[j].b});
else ans+=h.empty() or len(e[j].a)<
len(intersect((point){0,0},(point){cos(w),sin(w)},h.begin()->a,h.begin()->b-h.begin()->a));
}
// cerr<<i<<" ans="<<ans<<endl;
}
printf("%lld\n",ans);
return 0;
}
不知道为什么LOJ上用C++(NOI)
就会挂?可能是编译器版本的问题。