2023“钉耙编程”中国大学生算法设计超级联赛(2)Fencing the cows
题目大意
有 n n n个围栏点和 m m m个牧草点,你需要从这 n n n个点中选择尽可能少的围栏点,使得这些围栏点能包围所有牧草点。注意牧草点不能与围栏点相交。
有 t t t组数据。
1 ≤ t ≤ 10 , 1 ≤ n , m ≤ 500 1\le t\leq 10,1\leq n,m\leq 500 1≤t≤10,1≤n,m≤500
数据保证 ∑ n ≤ 4000 , ∑ m ≤ 4000 \sum n\leq 4000,\sum m\leq 4000 ∑n≤4000,∑m≤4000
题解
显然,这 n n n个点组成一个凸包,这 m m m个牧草点肯定在凸包的边的同一侧。但如果只判断是同侧,就可能会得出一个没有包围任何牧草点的凸包。所以,我们给每条边定为单向边,并规定 m m m个牧草点都要在这条边的左侧,这样就能避免上述情况。判断一个点是否在一条边的左侧可以用叉积来解决。
将 n n n个围栏点两两组成单向边,如果 m m m个牧草点不满足都位于边的左侧,那么这条边不合法。然后,对所有的合法边做一次 Floyd \text{Floyd} Floyd来求最小环即可。
时间复杂度为 O ( n 2 m + n 3 ) O(n^2m+n^3) O(n2m+n3)。
code
#include<bits/stdc++.h>
using namespace std;
const int inf=1e9;
int t,n,m,ans,f[505][505];
struct node{
int x,y;
}a[505],b[505];
long long check(node ax,node bx,node cx){
return 1ll*(ax.x-cx.x)*(bx.y-cx.y)-1ll*(bx.x-cx.x)*(ax.y-cx.y);
}
int main()
{
scanf("%d",&t);
while(t--){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d%d",&a[i].x,&a[i].y);
}
for(int i=1;i<=m;i++){
scanf("%d%d",&b[i].x,&b[i].y);
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(i==j){
f[i][j]=inf;continue;
}
f[i][j]=1;
for(int k=1;k<=m;k++){
if(check(a[i],a[j],b[k])<=0){
f[i][j]=inf;break;
}
}
}
}
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
f[i][j]=min(f[i][j],f[i][k]+f[k][j]);
}
}
}
ans=inf;
for(int i=1;i<=n;i++){
ans=min(ans,f[i][i]);
}
if(ans==inf) ans=-1;
printf("%d\n",ans);
}
return 0;
}