Roping the Field

该问题是一个数学和算法挑战,涉及到在一个带有障碍(谷物圈)的凸多边形田地中,找出农民John能布置的最大数量的直绳,绳子不能相交且不能穿过谷物圈。通过预处理点间距离和判断两点是否可连,使用区间动态规划方法求解最大绳子数。
摘要由CSDN通过智能技术生成

题目

Description

Farmer John is quite the nature artist: he often constructs large works of art on his farm. Today, FJ wants to construct a giant “field web”. FJ’s field is large convex polygon with fences along the boundary and fence posts at each of the N corners (1 <= N <= 150). To construct his field web, FJ wants to run as many ropes as possible in straight lines between pairs of non-adjacent fence posts such that no two ropes cross.

There is one complication: FJ’s field is not completely usable. Some evil aliens have created a total of G (0 <= G <= 100) grain circles in the field, all of radius R (1 <= R <= 100,000). FJ is afraid to upset the aliens, and therefore doesn’t want the ropes to pass through, or even touch the very edge of a grain circle. Note that although the centers of all the circles are contained within the field, a wide radius may make it extend outside of the field, and both fences and fence posts may be within a grain circle.

Given the locations of the fence posts and the centers of the circles, determine the maximum number of ropes that FJ can use to create his field web.

FJ’s fence pots and the circle centers all have integer coordinates X and Y each of which is in the range 0…1,000,000.

Input

Line 1: Three space-separated integers: N, G, and R

Lines 2…N+1: Each line contains two space-separated integers that are the X,Y position of a fence post on the boundary of FJ’s field.

Lines N+2…N+G+1: Each line contains two space-separated integers that are the X,Y position of a circle’s center inside FJ’s field.

Output

Line 1: A single integer that is the largest number of ropes FJ can use for his artistic creation.
Sample Input

5 3 1
6 10
10 7
9 1
2 0
0 3
2 2
5 6
8 3

Sample Output

1

Hint

Explanation of the sample:

A pentagonal field, in which all possible ropes are blocked by three grain circles, except for the rope between fenceposts 2 and 4.

题解

该题目需要注意以下几个点:
1、输入默认是以多边形顶点的顺序给出,不需要自己再次进行排序
2、题目里强调了是凸多边形,但是查看给出的部分测试用例后发现并不是凸多边形,因此经过斟酌后去掉了原本添加的顶点排序模块
3、题目里说明麦田怪圈是提到了圈的大小可能会包含顶点和栅栏,使我误以为两个不相邻顶点在圆圈内连线同样合法,但实际上查看测试用例后发现不是的,圈内顶点一律不合法就可以了。

除去以上几点,本身就是一个加了约束条件的区间dp问题,首先预处理判断哪些点之间可以相连,随后采用区间dp即可解决。

代码

首先对点间距离和点到圆心的距离进行建模,方便后续调用时无需重复计算。

double F_G[155][105];        
double F_F[155][155]; 
double distance_squire(Point a,Point b)        
{        
    return pow(a.x-b.x,2) + pow(a.y-b.y,2);        
}        
        
void compute_F_G()        
{        
    int i = 0,j = 0;        
    for(i=0;i<N;i++)        
    {        
        for(j=0;j<G;j++)        
        {        
            double distance = distance_squire(Fen[i],G_circle[j]);        
            F_G[i][j] = distance;        
        }        
    }        
}        
void compute_F_F()        
{        
    int i = 0,j = 0;        
    for(i=0;i<N;i++)        
    {        
        for(j=i;j<N;j++)        
        {        
            double distance = distance_squire(Fen[i],Fen[j]);        
            F_F[i][j] = distance;          
        }        
    }        
}     

需要注意的是,该函数计算以及存贮的并不是点间距离,而是点间距离的平方,之所以这样做,是因为后续关于点间距离的计算和比较均可以使用平方来完成,一点小小的简化计算。
所以读入半径R后也顺便平方一下。

R = pow(R,2);  

相连性检验

计算点与点之间是否可以相连

void compute_ok()        
{        
    int i = 0,j = 0;        
    for(i=0;i<N;i++)        
    {        
        for(j=i+2;j<N;j++)        
        {        
            if(check(i,j)&&(i!=0||j!=N-1))        
            {    
                ok[i][j] = 1;        
            }    
        }        
    }        
}  

其中的检验函数

bool check(int i,int j)        
{        
    int k=0;    
    for(k = 0;k<G;k++)        
    {    
        double a = F_G[i][k];        
        double b = F_G[j][k];        
        double c = F_F[i][j];    
        if(a > R + EPS && b > R + EPS)  
        {  
            if(a > b+c || b > a+c)  
                continue;  
            else  
            {  
                double h = (a*b - pow((a+b-c)/2,2)) / c;  
                if(h< R + EPS)        
                    return false;        
                else        
                    continue;        
            }  
        }  
        else  
            return false;   
    }    
    return true;        
}

循环判断两点之间的连线是否过圆,这里给出了两种判别条件,其中a为A点到圆心距离的平方,b为点到圆心距离的平方,c为AB间距离的平方

 if(a > R + EPS && b > R + EPS) 

若a,b 有任意小于半径,说明至少有一点在园内,不合法
根据勾股定理,我们易得,当三角形一边的平方大于另外两边的平方和是,该边对应的角为钝角,易得该条件下合法。

  if(a > b+c || b > a+c)  

否则需要计算以AB间距离作为底边的高h,已知三角形三边,可以通过秦九韶公式求得高h
S = 1 2 a 2 b 2 − ( a 2 + b 2 − c 2 2 ) 2 = 1 2 c h S = \frac{1}{2} \sqrt{a^{2} b^{2} -\left ( \frac{a^{2}+b^{2}-c^{2}}{2} \right ) ^{2}} =\frac{1}{2}ch S=21a2b2(2a2+b2c2)2 =21ch
易得
h 2 = 1 c 2 [ a 2 b 2 − ( a 2 + b 2 − c 2 2 ) 2 ] h^{2} = \frac{1}{c^{2}}\left [ a^{2} b^{2} -\left ( \frac{a^{2}+b^{2}-c^{2}}{2} \right ) ^{2} \right ] h2=c21[a2b2(2a2+b2c2)2]
故可以直接通过代入a、b、c的平方来求得h。
当h>R时,合法,否则不合法

区间DP

使用递归的方式求解,为了节省计算,预设矩阵来存贮函数计算结果防止重复计算。
因为绳索之间不能交叉,因此整体使用区间DP的方式,在(start,end)的区间内,判断每一个中间节点,取其中的最大值作为该函数的返回值

DP_F[start][end] = max(DP_F[start][end],DP(start,k)+DP(k,end)+ok[start][end]);

DP函数如下所示

int DP(int start,int end)        
{    
    if(is_DP[start][end])        
    {      
        return DP_F[start][end];        
    }      
    if(end-start <= 1)        
    {      
        is_DP[start][end]= true;      
        return 0;       
    }      
    int k = start + 1;        
    for(k=start+1;k<end;k++)        
    {        
        DP_F[start][end] = max(DP_F[start][end],DP(start,k)+DP(k,end)+ok[start][end]);        
    }        
    is_DP[start][end] = 1;      
    return DP_F[start][end];        
}

源码

源代码如下所示

#include <iostream>        
#include <math.h>       
#include<vector>        
#include<algorithm>      
using namespace std;        
        
int N,G;        
long long R;        
# define EPS 1e-6        
        
struct Point        
{        
    long long x;        
    long long y;        
};        
        
        
Point G_circle[105];        
Point Fen[155];        
        
double F_G[155][105];        
double F_F[155][155];        
        
int ok[155][155];        
int DP_F[155][155];        
bool is_DP[155][155];      
bool check(int i,int j)        
{        
    int k=0;    
    for(k = 0;k<G;k++)        
    {    
        double a = F_G[i][k];        
        double b = F_G[j][k];        
        double c = F_F[i][j];    
        if(a > R + EPS && b > R + EPS)  
        {  
            if(a > b+c || b > a+c)  
                continue;  
            else  
            {  
                double h = (a*b - pow((a+b-c)/2,2)) / c;  
                if(h< R + EPS)        
                    return false;        
                else        
                    continue;        
            }  
        }  
        else  
            return false;   
    }    
    return true;        
}        
        
double distance_squire(Point a,Point b)        
{        
    return pow(a.x-b.x,2) + pow(a.y-b.y,2);        
}        
        
void compute_F_G()        
{        
    int i = 0,j = 0;        
    for(i=0;i<N;i++)        
    {        
        for(j=0;j<G;j++)        
        {        
            double distance = distance_squire(Fen[i],G_circle[j]);        
            F_G[i][j] = distance;        
        }        
    }        
}        
void compute_F_F()        
{        
    int i = 0,j = 0;        
    for(i=0;i<N;i++)        
    {        
        for(j=i;j<N;j++)        
        {        
            double distance = distance_squire(Fen[i],Fen[j]);        
            F_F[i][j] = distance;          
        }        
    }        
}        
        
void compute_ok()        
{        
    int i = 0,j = 0;        
    for(i=0;i<N;i++)        
    {        
        for(j=i+2;j<N;j++)        
        {        
            if(check(i,j)&&(i!=0||j!=N-1))        
            {    
                ok[i][j] = 1;        
            }    
        }        
    }        
}        
        
int DP(int start,int end)        
{    
    if(is_DP[start][end])        
    {      
        return DP_F[start][end];        
    }      
    if(end-start <= 1)        
    {      
        is_DP[start][end]= true;      
        return 0;       
    }      
    int k = start + 1;        
    for(k=start+1;k<end;k++)        
    {        
        DP_F[start][end] = max(DP_F[start][end],DP(start,k)+DP(k,end)+ok[start][end]);        
    }        
    is_DP[start][end] = 1;      
    return DP_F[start][end];        
}        
    
      
int main()        
{        
    //读取数据        
    scanf("%d %d %lld",&N,&G,&R);        
    int i = 0,j = 0;        
    long long x=0,y=0;        
    R = pow(R,2);  
    for(i=0;i<N;i++)        
    {        
        scanf("%lld %lld",&x,&y);        
        Fen[i].x = x;        
        Fen[i].y = y;        
    }        
    for(i=0;i<G;i++)        
    {        
        scanf("%lld %lld",&x,&y);        
        G_circle[i].x = x;        
        G_circle[i].y = y;        
    }        
          
    compute_F_G();        
    compute_F_F();        
    compute_ok();        
    int out = DP(0,N-1);        
    printf("%d\n",out);    
}  

测试用例

该题目的测试用例如下
测试用例下载

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值