LA 4258 Metal
题目大意:
平面上有n个点,任意两点的x坐标不同.统计有多少种方案能将其连成单调多边形.满足多边形非相邻边不能有公共点,任意两条边不能相交,且与任意与y轴平行的直线与多边形的公共部分是一个点或一条线段(或者说该直线只能与多边形交于1个或者两个点).
题目分析:
由满足条件可知,对于多边形的上下边缘一定不会回折(如上图),因为若某条线延伸出去又折回来,那么一定会使得公共部分变成多条线段.所以两边经过的点x坐标一定是单调的.
还需要考虑是否发生相交的问题,直接预处理.判断两点间的点在这条直线的上方还是下方即可.
所以可以选择x坐标从小到大来递推,设dp(i,j)表示上边缘到达i点,下边缘到达j点,且0~max(i,j)的点都已经连起来的方案数.
要保证不重复不遗漏,那么下一个点就是t=max(i,j)+1.
当然需要注意到最终结点的方案数,否则方案数会被计算两次(从i到n-1和从j到n-1).
代码:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=50+5;
struct Point {
int x,y;
bool operator < (const Point& rhs) const {
return x<rhs.x;
}
void input() {
scanf("%d%d",&x,&y);
}
}P[maxn];
int Gu[maxn][maxn],Gd[maxn][maxn];
void init(int n)
{
for(int i=0;i<n;i++)
for(int j=i+1;j<n;j++) {
Gu[i][j]=Gd[i][j]=1;
double k=1.*(P[i].y-P[j].y)/(P[i].x-P[j].x);
double b=P[i].y-k*P[i].x;
for(int p=i+1;p<j;p++) {
if(P[p].y>=k*P[p].x+b) Gu[i][j]=0;//若p在i-j直线上方,i-j不能形成上边缘
if(P[p].y<=k*P[p].x+b) Gd[i][j]=0;//若p在i-j直线下方,i-j不能形成下边缘
}
}
}
int dp[maxn][maxn];//dp[i][j]表示上边缘到i点,下边缘到j点的方案数
void solve(int n)
{
memset(dp,0,sizeof(dp));
dp[0][1]=dp[1][0]=1;
for(int i=0;i<n-1;i++)//i和j不要循环到n-1,不然答案会被算两边
for(int j=0;j<n-1;j++) if(dp[i][j]) {
int t=max(i,j)+1;
if(t==n-1&&Gu[i][t]&&Gd[j][t]) {
dp[n-1][n-1]+=dp[i][j];
continue;
}
if(i!=n-1&&Gu[i][t]) dp[t][j]+=dp[i][j];
if(j!=n-1&&Gd[j][t]) dp[i][t]+=dp[i][j];
}
printf("%d\n",dp[n-1][n-1]);
}
int main()
{
int T,n;
scanf("%d",&T);
while(T--) {
scanf("%d",&n);
for(int i=0;i<n;i++) P[i].input();
sort(P,P+n);
init(n);
solve(n);
}
return 0;
}