题解 P5302 【[GXOI/GZOI2019]特技飞行】

cosmicAC

2019-04-16 17:27:55

Solution

这道题显然是由三个部分组成的: 1. 找到所有的直线交点 2. 统计每一个点是否被矩形覆盖 3. 决定【擦身而过】特技的最多次数 首先是部分一,容易观察到$y_{x,1}$数组的每一个逆序对对应一个交点。所以只要CDQ或者set求出所有的逆序对,然后写一个直线求交点即可。直接上计算几何板子即可。由于此题中的斜率不会是$\infty$,所以也可以解析暴力。 然后是部分二,这里不需要kd-tree或者树套树。可以先把坐标系转$45\degree$,即$(x,y)\rightarrow(x+y,y-x)$,然后就变成了矩形加单点求和。可以差分,就变成了单点加矩形求和。于是可以离线+扫描线,用一个线段树或BIT维护即可。此处要注意实数坐标的精度误差。 最后是部分三,首先可以注意到,每次两条航线相交就相当于交换一个排列中的相邻两个数。所以可以把所有交点按照从左往右顺序写成一个序列,比如$[(1,2),(2,3),(3,4),(2,3),(1,2)]$。我首先想到的是一个假算法,把每个数对的第一次出现当成左括号,第二次当成右括号,比如刚才的序列就写成`([{])`。然后消去尽量多构成合法括号序列的括号对。可惜这样做是有反例的,比如下面的图。 ![](https://i.loli.net/2019/04/16/5cb59c77e5272.png ) 交点序列是$[(1,2),(2,3),(3,4),(1,2),(4,5),(2,3),(1,2),(3,4),(2,3)]$,然而最优解是消去$[(1,2),(2,3),(1,2),(2,3),(1,2),(2,3)]$。 就在手工模拟这个反例的时候,我发现了一些规律:貌似**左侧最上方的点和右侧最上方的点之间的折线最多只会有两段**。于是我可以把这两段消去,把剩下的半段折线强行掰直。比如上面的图第一次消去之后是这样的 ![](https://i.loli.net/2019/04/16/5cb59ec1d392e.png) 每次掰直一段折线时,【对向交换】的次数加1。然后似乎贪心的递归下去做就是对的了。**本人无法证明其正确性。** 代码如下: ```cpp #include<bits/stdc++.h> #define eps 1e-12 using namespace std; using ll=long long; const int maxn=1<<20; struct point{double x,y;}; point operator-(point a,point b){return{a.x-b.x,a.y-b.y};} point operator+(point a,point b){return{a.x+b.x,a.y+b.y};} point operator*(double a,point b){return {b.x*a,b.y*a};} double operator*(point a,point b){return a.x*b.y-a.y*b.x;} point jd(point a,point b,point c,point d){ return a+((c-a)*(d-c))/((b-a)*(d-c))*(b-a); } struct segTree{ int a[1<<24],L[1<<24],R[1<<24],tot,rt; void ins(int x,int v,int&p,int tl=-2e8,int tr=2e8){ if(!p)p=++tot; if(tl==tr){a[p]+=v;return;} int mid=tl+tr>>1; if(x<=mid)ins(x,v,L[p],tl,mid);else ins(x,v,R[p],mid+1,tr); a[p]=a[L[p]]+a[R[p]]; } int qry(int l,int r,int p=1,int tl=-2e8,int tr=2e8){ if(l<=tl&&tr<=r)return a[p]; int mid=tl+tr>>1,re=0; if(l<=mid)re=qry(l,r,L[p],tl,mid); if(r>mid)re+=qry(l,r,R[p],mid+1,tr); return re; } }tr; int n,A,B,C,x0,x1,k,Y0[maxn],m,rnk[maxn],sa[maxn]; ll ans; struct st{int v,id;}a[maxn],b[maxn]; struct ev{double x,y;int tp,vl;};vector<ev> e; void cdq(int l,int r){ if(l==r)return; int mid=l+r>>1; cdq(l,mid),cdq(mid+1,r); int i=l,j=mid+1,k=0; while(i<=mid||j<=r) if(j>r||i<=mid&&a[i].v<a[j].v){ for(int t=mid+1;t<j;t++,m++){ point p=jd({x0,Y0[a[i].id]},{x1,a[i].v}, {x0,Y0[a[t].id]},{x1,a[t].v}); e.push_back({p.x+p.y,p.y-p.x,1,0}); } b[++k]=a[i++]; }else b[++k]=a[j++]; copy(b+1,b+1+k,a+l); } int main(){ scanf("%d%d%d%d%d%d",&n,&A,&B,&C,&x0,&x1); for(int i=1;i<=n;i++)scanf("%d",Y0+i),sa[i]=i; for(int i=1,x;i<=n;i++)scanf("%d",&x),a[i]={x,i}; sort(sa+1,sa+1+n,[](int x,int y){return a[x].v<a[y].v;}); for(int i=1;i<=n;i++)rnk[sa[i]]=i; scanf("%d",&k); while(k--){ int p,q,r;scanf("%d%d%d",&p,&q,&r); e.insert(e.end(),{ {p+q-r-eps,q-r-p,0,1},{p+q-r-eps,q+r-p,0,-1}, {p+q+r+eps,q-r-p,0,-1},{p+q+r+eps,q+r-p,0,1}}); } cdq(1,n); sort(e.begin(),e.end(),[](ev a,ev b){return a.x<b.x;}); for(ev&x:e)if(!x.tp)tr.ins(x.y,x.vl,tr.rt);else if(tr.qry(-2e8,floor(x.y-eps)))ans+=C; //此处必须要floor,因为把double转成int默认是向零取整 ll x=1ll*m*A+ans;int cnt=0; for(int i=1;i<=n;i++){ int p=rnk[i],q=sa[i]; if(p!=i){ sa[p]=q,rnk[q]=p; cnt++; } } ll y=1ll*cnt*A+1ll*(m-cnt)*B+ans; printf("%lld %lld\n",min(x,y),max(x,y)); return 0; } ```