Description
将凸多边形划分成 n−2 个三角形,使得每个三角形内的点的个数为偶数。要求划分线上不能有羊经过。
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 5Sample 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[i…j] 表示由顶点 i…j 围成的多边形的方案个数,转移方程:
dp[i][j]=∑{dp[i][k]×dp[k][j]∣i<k<j,G[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));
}