Luogu P4518 [JSOI2018]绝地反击
Link
假如我们现在确定了一个答案,然后要检测它是否可行。
我们求出所有飞船能够到达的圆弧,总共有\(2n\)个端点。
如果存在一组合法解,那么一定存在一组正多边形有至少一个顶点在端点上的解,那么我们暴力建边然后求二分图最大匹配即可。
利用爬山+二分优化即可通过。
正解大概是说首先圆弧对应的偏转角度可以\(\bmod \frac{2\pi}n\),然后用退流优化dinic做到\(O(n^3\log n)\)。
#include<cmath>
#include<cstdio>
#include<cstring>
#include<utility>
#include<algorithm>
using ld=long double;
using pi=std::pair<ld,ld>;
const int N=207;const ld eps=1e-10,PI=acos(-1);
int n,R,mat[N],vis[N],e[N][N];pi w[N],b[N];
struct node{int x,y;ld d;node(int a=0,int b=0):x(a),y(b),d(sqrt(a*a+b*b)){}}a[N];
int read(){int x;scanf("%d",&x);return x;}
int sgn(ld x){return x>eps? 1:x<-eps? -1:0;}
ld sqr(ld x){return x*x;}
ld Bonus_solve(){ld ans=0;for(int i=0;i<n;++i) ans=std::max(ans,a[i].d);return ans;}
int dfs(int u)
{
for(int v=0;v<n;++v) if(!vis[v]&&e[u][v]) if(vis[v]=1,!~mat[v]||dfs(mat[v])) return mat[v]=u,1;
return 0;
}
int check(int id,int flg,ld r)
{
ld l=(sqr(a[id].x)+sqr(a[id].y)+r-R*R)/(2*a[id].d),h=sqrt(std::max((ld)0,r-l*l));
ld dx=-a[id].x/a[id].d,dy=-a[id].y/a[id].d,px=a[id].x+dx*l,py=a[id].y+dy*l;
dy=-dy,std::swap(dx,dy),dx*=h,dy*=h,px-=flg*dx,py-=flg*dy,b[0]=pi(px,py),memset(mat,-1,4*n);
for(int i=1;i<n;++i) b[i]=pi(px*w[i].second-py*w[i].first,px*w[i].first+py*w[i].second);
for(int i=0;i<n;++i) for(int j=0;j<n;++j) e[i][j]=sgn(sqr(a[i].x-b[j].first)+sqr(a[i].y-b[j].second)-r)<=0;
for(int i=0;i<n;++i) if(memset(vis,0,4*n),!dfs(i)) return 0;
return 1;
}
int main()
{
n=read(),R=read();ld ans=0,lim=0;
for(int i=0,x,y;i<n;++i) x=read(),y=read(),a[i]=node(x,y);
if(!R) return !printf("%.10Lf",Bonus_solve());
for(int i=1;i<n;++i) w[i]=pi(sin(2*PI*i/n),cos(2*PI*i/n));
std::random_shuffle(a,a+n);
for(int i=0;i<n;++i) lim=std::max(lim,sqr(a[i].x)+sqr(a[i].y)<=R+eps? R-a[i].d:a[i].d-R),ans=std::max(ans,a[i].d+R);
for(int i=0;i<n;++i)
{
if(!a[i].x&&!a[i].y) continue;
for(int j:{-1,1})
{
ld l=lim,r=std::max(std::min(a[i].d+R,ans-eps),lim);
if(!check(i,j,sqr(r))) continue;
for(ld mid;l+eps<r;) mid=(l+r)/2,check(i,j,sqr(mid))? r=ans=mid:l=mid;
}
}
printf("%.10Lf",ans);
}