LA 4258 Metal (递推)

来源:互联网 发布:wildfly 端口配置 编辑:程序博客网 时间:2024/05/18 11:47

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;}
0 0