BZOJ2087 POI2010 Sheep


POI2010 题解整理

Description

将凸多边形划分成 n2 个三角形,使得每个三角形内的点的个数为偶数。要求划分线上不能有羊经过。

Input

  • Line 1:由空格隔开的3个整数n(4<=n<=600),k(2<=k<=20000),m(2<=m<=20000),n表示牧场的顶点数,k表示羊的个数。
  • Line 2~n+1:顶点的坐标 xi yi
  • Line n+2~n+k+1:羊的坐标, pi pj 。(-15000<= xi yi pi pj <=15000)
    输入保证羊个数为偶数,且羊的坐标严格在凸多边形内。

Output

牧场能划分的总方案数被m除的余数。

Sample Input

5 4 10
5 5
3 0
-1 -1
-3 4
1 10
1 0
-1 0
1 6
-2 5

Sample Output

3


Solution

对于凸多边形上的顶点i,j,当且仅当两点之间的连线没有经过某只羊,并且两侧的羊的个数为偶数(显然如果分出奇数,对于剩下的小凸多边形就不可能满足题意)时,连线。

由于n<=600,所以考虑 O(N3) 级别的写法。我们如果对于一个基点,从左往右依次枚举其他点是否能进行连边,此时再依次收集在这条边某一侧的羊的个数,就能依次判断了。

完成上述操作需要用到计算几何的少量知识,此处考虑到精度问题,我们采用向量的叉积求解:

  • 对于两组向量 a⃗  (x1,y1), b⃗  (x2,y2),对于w=x1*y2-x2*y1:
    • 若w<0,则 a⃗  b⃗  的逆时针方向;
    • 若w>0,则 a⃗  b⃗  的顺时针方向;
    • 若w=0,则 a⃗  b⃗  共线。

按照上述定义进行极角排序即可。

接下来就是dp部分,与Catalan数的计数原理类似,定义 dp[ij] 表示由顶点 ij 围成的多边形的方案个数,转移方程:

dp[i][j]={dp[i][k]×dp[k][j]i<k<jG[i][k]G[k][j]}

Code

#include <bits/stdc++.h>
#define M 605
#define N 20005
using namespace std;
inline void Rd(int &res){
    res=0;char c,f=1;
    while(c=getchar(),c<48&&c!='-');
    do if(c=='-')f=-1;
    else res=(res<<3)+(res<<1)+(c^48);
    while(c=getchar(),c>47);
    res*=f;
}//还要增加负数的读入 
struct vec{
    int x,y;
//  vec(int _x=0,int _y=0):x(_x),y(_y) {}//等价于{x=_x,y=_y;} 
    vec operator - (const vec &a)const{
        return (vec){x-a.x,y-a.y};
    }//写成(x-a.x,y-a.y)居然不报错 
    int operator * (const vec &a)const{
        return x*a.y-a.x*y;
    }
}a[M],b[N],st;
bool cmp(vec a,vec b){return (a-st)*(b-st)<0;}
bool G[M][M];int dp[M][M];
int n,m,P;
int dfs(int L,int R){
    int &res=dp[L][R];
    if(~res)return dp[L][R];
    res=0;
    for(int k=L+1;k<R;k++)
        if(G[L][k]&&G[k][R])res=(res+1ll*dfs(L,k)*dfs(k,R))%P;
    return res;
}
int main(){
    Rd(n),Rd(m),Rd(P);
    for(int i=1;i<=n;i++)Rd(a[i].x),Rd(a[i].y);
    for(int i=1;i<=m;i++)Rd(b[i].x),Rd(b[i].y);
    st=a[1];
    sort(a+2,a+n+1,cmp);
    for(int i=1;i<n;i++){
        st=a[i];
        sort(b+1,b+m+1,cmp);
        int res=0;
        for(int j=i+1;j<=n;j++){
            while(res<m&&(b[res+1]-st)*(a[j]-st)<=0)++res;
            if(res&1||(res&&(b[res]-st)*(a[j]-st)==0))continue;
            G[i][j]=G[j][i]=true;
        }
    }
    memset(dp,-1,sizeof(dp));
    for(int i=1;i<n;i++)dp[i][i+1]=1;
    printf("%d\n",dfs(1,n));
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值