题解:P16127 [ICPC 2018 NAIPC] Cut It Out!
lailai0916 · · 题解
题意简述
凸多边形
求最终仅剩
解题思路
每条切割直线都唯一对应
将
原因在于凸性。沿边
因此,若这一侧的点被某条已切割边排除,这条可见边链必然包含该侧环上最近的已切割边,最近边的半平面也已经排除了它。另一侧同理。其余已切割边不能再缩短由
这使尚未切割的边自然分成若干连续区间。定义
设区间中首先切割边
任意切割顺序都有区间内的第一条边,所以转移不会漏掉最优解;反过来,先切枚举的边,再执行两个子区间的最优顺序,就能实现转移得到的代价。
下面计算
输入顶点按顺时针给出,所以一条从点
记常数项为
对每条待切边,先用 lo 和 hi 中。再预处理每条 lmt 和 rmt 中,没有对应限制时用无穷界。每次转移将初始区间与边
所有叉积都先使用整数计算。坐标差的绝对值不超过 long long,因此平行判定不依赖浮点误差。除法、长度和动态规划再使用 long double。
最后处理环。将边序列复制一份,并按区间长度从小到大计算。枚举第一条切割边
设
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
using ld=long double;
const int N=205;
const ld inf=1e30L;
struct P
{
ll x,y;
P operator-(P a){return {x-a.x,y-a.y};}
};
P a[N],b[N];
ld lo[N],hi[N],len[N],lmt[N][N],rmt[N][N],f[N*2][N*2];
ll cross(P a,P b){return a.x*b.y-a.y*b.x;}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int m;
cin>>m;
for(int i=0;i<m;i++)cin>>a[i].x>>a[i].y;
a[m]=a[0];
int n;
cin>>n;
for(int i=0;i<n;i++)cin>>b[i].x>>b[i].y;
b[n]=b[0];
for(int i=0;i<n;i++)
{
P d=b[i+1]-b[i];
len[i]=sqrtl((ld)d.x*d.x+(ld)d.y*d.y);
lo[i]=-inf;
hi[i]=inf;
for(int j=0;j<m;j++)
{
P e=a[j+1]-a[j];
ll u=cross(e,b[i]-a[j]),v=cross(e,d);
if(v>0)hi[i]=min(hi[i],-(ld)u/v);
else if(v<0)lo[i]=max(lo[i],-(ld)u/v);
}
for(int j=0;j<n;j++)
{
lmt[i][j]=-inf;
rmt[i][j]=inf;
P e=b[j+1]-b[j];
ll u=cross(e,b[i]-b[j]),v=cross(e,d);
if(v>0)rmt[i][j]=-(ld)u/v;
else if(v<0)lmt[i][j]=-(ld)u/v;
}
}
for(int i=2;i<=n;i++)
{
for(int j=0;j+i<2*n;j++)
{
f[j][j+i]=inf;
for(int k=j+1;k<j+i;k++)
{
int u=k%n,v=j%n,w=(j+i)%n;
ld l=max({lo[u],lmt[u][v],lmt[u][w]}),r=min({hi[u],rmt[u][v],rmt[u][w]});
f[j][j+i]=min(f[j][j+i],f[j][k]+f[k][j+i]+(r-l)*len[u]);
}
}
}
ld ans=inf;
for(int i=0;i<n;i++)ans=min(ans,f[i][i+n]+(hi[i]-lo[i])*len[i]);
cout<<fixed<<setprecision(12)<<ans<<'\n';
return 0;
}