题解:P17358 [ECNA 2024] Fences Make Good Neighbors
lailai0916 · · 题解
题意简述
给定一个凸
任何对角线都不能经过这两个点。求所有新增对角线的最小长度和;不存在合法剖分时输出 IMPOSSIBLE。
解题思路
固定一种三角剖分,把每个三角形看成一个点,共用一条对角线的两个三角形之间连边,得到一棵树。原因是凸多边形的一条对角线会将其分成两个较小的多边形,两侧的三角形之间只能通过这条对角线相连;递归地看,每次都是将两棵树用一条边连接。
这棵树中,两个房屋所在三角形之间的路径长度应当为
设两个房屋为
若
否则,
因此,原要求等价于 整个剖分恰好使用两条将房屋分开的对角线。只需维护一个不超过
按题目给定顺序将顶点编号为
为了避免在合并时重复计算长度,将每个区间的闭合边也算入该区间。定义
令
相邻两个顶点之间没有三角形,也没有新围栏,因此
对于至少包含三个顶点的区间,枚举与闭合边
其中
每条新增对角线恰好作为一个子区间的闭合边被计费一次。计数也只会在合并时相加,不会减少,因此舍去超过
最终取
代码将至多六种计数组合直接展开。时间复杂度为 long long 精确判断共线及两侧关系,只有长度计算使用浮点数。
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
const int N=505;
const double inf=1e100;
struct Point
{
int x,y;
}a[N],b[2];
double f[N][N][3],dis[N][N];
int cnt[N][N];
bool vis[N][N];
ll cross(Point a,Point b,Point c)
{
return 1LL*(b.x-a.x)*(c.y-a.y)-1LL*(b.y-a.y)*(c.x-a.x);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i].x>>a[i].y;
for(int i=0;i<2;i++)cin>>b[i].x>>b[i].y;
for(int i=1;i<=n;i++)
{
for(int j=i+1;j<=n;j++)
{
ll x=cross(a[i],a[j],b[0]),y=cross(a[i],a[j],b[1]);
vis[i][j]=x&&y;
cnt[i][j]=(x<0)!=(y<0);
dis[i][j]=hypot(a[i].x-a[j].x,a[i].y-a[j].y);
if(j==i+1||i==1&&j==n)dis[i][j]=0;
for(int k=0;k<3;k++)f[i][j][k]=inf;
if(j==i+1)f[i][j][0]=0;
}
}
for(int i=n;i>=1;i--)
{
for(int j=i+2;j<=n;j++)
{
if(!vis[i][j])continue;
for(int k=i+1;k<j;k++)
{
int t=cnt[i][j];
f[i][j][t]=min(f[i][j][t],f[i][k][0]+f[k][j][0]+dis[i][j]);
f[i][j][t+1]=min(f[i][j][t+1],min(f[i][k][0]+f[k][j][1],f[i][k][1]+f[k][j][0])+dis[i][j]);
if(!t)f[i][j][2]=min(f[i][j][2],min({f[i][k][0]+f[k][j][2],f[i][k][1]+f[k][j][1],f[i][k][2]+f[k][j][0]})+dis[i][j]);
}
}
}
if(f[1][n][2]>=inf/2)cout<<"IMPOSSIBLE"<<'\n';
else cout<<fixed<<setprecision(10)<<f[1][n][2]<<'\n';
return 0;
}