题解 P3433 【[POI2005]PRA-Dextrogyrate Camel】

Hope2075

2019-03-30 16:55:33

Solution

这题成为我人生第一道灰题(可能很快就不是了) 先平移坐标系,把起点移到原点,方便讨论 首先看看什么样的路线是合法的 ![](https://cdn.luogu.com.cn/upload/pic/55436.png) 似乎有点什么特点 ![](https://cdn.luogu.com.cn/upload/pic/55437.png) ![](https://cdn.luogu.com.cn/upload/pic/55438.png) 瞎猜一个结论:除了起点和终点外,,其它部分只要突出就可以 而且可以得到结论:最多转一圈 否则,当回来的时候,会发现起点已经被完全包围了 如果想绕进去,则会把自己困住 所以合法路径的特点:除了起点外,其它角度正确,且只绕一圈 考虑把点按极角排序,2号点极角为0,其它为从2号点顺时针旋转的角度 DP的时候,可以简单地认为从2号点出发,只能向顺时针方向前进 先考虑状态 如果只记录每个点,则不能判断能否继续走 如果再记录从哪个点来? 直接记录是$O(n^2)$状态 转移时,需要枚举所有可能的点对 这样是$O(n^3)$的,还有较多的计算,可能会超时 考虑一下性质:对每个点,将所有能到达它的点恰当地排序,可以满足接下来可以走的点数单调减少 如果不理解,可以看看这个图 ![](https://cdn.luogu.com.cn/upload/pic/55441.png) 如果恰当地排序,则当选出的绿色点向下移动时,可以选的红色点向上减少 反过来也一样:选出的红色点向下移动时,可以选的绿色点会向上减少 可以想到单调队列或类似做法 但是这样会比较复杂 所以我就换了一个思路:记录当前点答案不小于$k$时,使得可选点范围最大的源点 这样可以发现DP值满足类似的性质,也就是:可以二分 转移时,枚举之前的点,二分最优答案,判断是否合法即可 另外,因为可能存在答案更差而且可选范围减小的结果,所以每完成一个点,从后向前扫一遍,把不优的DP值改成更优的就行 具体实现 为了防止精度问题,全文用int和叉积 按极角排序: 我的比较函数:先判断是否在基准线两侧 如果是,则可以直接得到结果 否则,两个点的极角差不会大于180度,可以用叉积判断 转移 枚举时判合法,只要这两个点相对位置正确就可以(用叉积判) 二分时,需要判断两条线段间夹角是否合法,也是用的叉积 二分完了要注意:如果一定不合法,要忽略这个答案 ![](https://cdn.luogu.com.cn/upload/pic/55444.png) 这种情况下,左下角那一个没有连上线段的点不会有DP值,也就不会更新后面的点 不然就可以开心地WA了 代码: 这里有些区别,请自行理解 包含输出方案功能部分,把```if(0)```改成```if(1)```就行 ```cpp #include<iostream> #include<algorithm> using namespace std; const int N=1024; struct vec{ int x,y; }; vec operator+(vec a,vec b){ return (vec){a.x+b.x,a.y+b.y}; } vec operator-(vec a,vec b){ return (vec){a.x-b.x,a.y-b.y}; } int operator*(vec a,vec b){ return a.x*b.y-a.y*b.x; } vec base; inline int sgn(int num){ if(num>0)return 1; else if(num<0) return -1; else return 0; } bool operator<(vec a,vec b){ if(sgn(a*base)*sgn(b*base)==-1){ return a*base>0; }else{ return a*b<0; } } int n; vec list[N]; int dp[N][N]; vec F,P,C; int l,r,mid; int road[N],cnt; int maxn,maxid; int main(){ ios::sync_with_stdio(false); cin>>n; cin>>list[n].x>>list[n].y; for(int i=1;i<n;i++){ cin>>list[i].x>>list[i].y; list[i]=list[i]-list[n]; } list[n].x=list[n].y=0; base=list[1]; sort(list+2,list+n); dp[1][0]=dp[1][1]=n; for(int i=2;i<=n;i++){ C=list[i]; for(int j=1;j<i;j++){ P=list[j]; if(P*C>0)continue; l=0,r=n+1; while(l!=r){ mid=((l+r)>>1); if(dp[j][mid]==0){ r=mid; continue; } F=list[dp[j][mid]]; if((P-F)*(C-P)<=0){ l=mid+1; continue; }else{ r=mid; } } if(dp[j][l-1]==0)continue; //这里一定要判,否则会产生不合法转移 if(maxn<l){ maxn=l; maxid=i; } if(dp[i][l]==0){ dp[i][l]=j; }else{ F=list[dp[i][l]]; if((C-P)*(C-F)<0){ dp[i][l]=j; } } } for(int j=n;j>=0;j--){ if(dp[i][j]==0)dp[i][j]=dp[i][j+1]; else{ if(dp[i][j+1]==0)continue; F=list[dp[i][j]]; P=list[dp[i][j+1]]; if((C-P)*(C-F)<0){ dp[i][j]=dp[i][j+1]; } } } } cout<<maxn-1<<endl; road[cnt++]=n; for(int p=dp[n][maxn],t=maxn;p!=n;--t,p=dp[p][t]){ road[cnt++]=p; } if(0){ for(int i=cnt-1;i>=0;i--){ cout<<road[i]<<" "; } } } ```