Description
在远古的YL国大地上,有n个祭坛,每个祭坛上四个方向写有“艄、毜、鼛、瓯”四个大字,其实这在YL国古代分别是“东、南、西、北”的意思。
YL国每年都要举行祈福消灾的祭祀活动,这个时候,每个祭坛都要在艄毜鼛瓯四个方向中选一个方向,祭坛将向这个方向发出一道隐形的光线,如果两个祭坛发出的光线相交,或者祭坛发出的光线经过了别的祭坛,则祭祀不仅不能成功还将遭到上天的惩罚,如果这些条件都满足则祭祀将成功,YL国在接下来的一年内将不会有任何灾难,甚至只会有人出生不会有人死亡。
抽象的来说,如果我们以“艄”方向为x轴,“瓯”方向为y轴,建立笛卡尔坐标系,那么每个祭坛将会对应一个整点。每个点向上下左右四个方向之一连出一条射线,这些射线不能相交且射线不能经过除了发出点之外的其他点}。
现在他们又到了祭祀活动的时候,他们想知道,有多少种方法选择每个祭坛的方向},使得自己的祭祀能成功?输出方案数对998244353取模后的值}。
Input
第一行一个正整数n。
接下来n行,第i + 1行两个整数x_i, y_i,表示第i个祭坛在题目中所述的坐标系下的坐标为(x_i, y_i)。
Output
输出一行一个整数,表示要求的方案数对998244353取模后的值。
Sample Input
输入1:
1
1 1
输入2:
2
1 1
2 2
输入3:
6
0 0
0 1
0 2
0 3
0 4
0 5
输入4:
5
1 3
-4 6
2 4
1 6
5 9
输入5:
10
175470546 566770243
501153312 923840801
-36922529 -888266785
-587403745 908979876
-483726071 -96937502
991096990 -783436017
766700568 -679180551
-601529754 815529478
961445805 303768338
245415738 325847411
Sample Output
输出1:
4
样例1解释:只有一个祭坛,显然四个方向都可以发射。
输出2:
14
样例2解释:
对于所有的4 × 4 = 16种情况中,只有两种不可行:
1号祭坛向上,2号向左。
1号向右,2号向下
输出3:
144
样例3解释:
最上面的祭坛可以向左中右三个方向连出射线,最下面的祭坛可以向右下左三个方向连出射线,中间4个祭坛可以向左右连出射线,方案数为3 × 2 × 2 × 2 × 2 × 3 = 144。
输出4:
117
输出5:
24341
Data Constraint
对于前30%的数据,n ≤ 9。
对于前40%的数据,n ≤ 18。
对于前60%的数据,n ≤ 36。
对于前100%的数据,n ≤ 54,对于所有i, j,有x_i ≠ x_j或y_i ≠ y_j,且|x_i|, |y_i| ≤ {10} ^ 9。
这个题目好神奇啊,一开始还真被第5个样例吓着了。。。话说那四个大字是怎么回事。。囧
好了,言归正传,题目大意大概是这样的:
平面上有n个点,每个点都要沿上下左右其中一个方向射出一条射线。问这些射线之间两两不相交且不与除端点外的任何一个点相交的方案数。
n<55 (要DP的节奏啊)
首先,如果某个点往另一个点的方向射线,显然是不合法的,一个点在另一个点的射程上也是不合法的。
考虑按x递增的顺序来进行DP。那么我们只需要判定某个点对某个方向放出射线是否可行。不难发现只需要记录以下几个值就可以进行判定:
往上的点的纵坐标最小值
往下的点的纵坐标最大值
往右的点的纵坐标的最大值和最小值
那么剩下的就是一个O(n5)的DP就可以解决问题了。
注意要排除多余状态以及要使用滚动数组。
贴贴程序~~
#include <cstdio>
#include <iostream>
#include <cmath>
#include <cstring>
#include <algorithm>
#define N 56
#define mo 998244353
using namespace std;
struct Record
{
int x,y;
}a[N];
int F[N][N][N][N][2]; //up down left right roll
bool bz;
bool cmp(Record a,Record b)
{
return a.x<b.x || (a.x==b.x && a.y<b.y);
}
int Min(int x,int y)
{
if (!y) return x;
if (!x) return y;
if (a[x].y<a[y].y) return x;
else return y;
}
int Max(int x,int y)
{
if (!y) return x;
if (!x) return y;
if (a[x].y>a[y].y)return x;
else return y;
}
bool pd(int i,int j,int k)
{
if (j==1 && a[k].x==a[i+1].x && a[k].y>a[i+1].y) return false;
if (j==2 && a[k].x==a[i+1].x && a[k].y<a[i+1].y) return false;
if (j==3 && a[k].y==a[i+1].y && a[k].x<a[i+1].x) return false;
if (j==4 && a[k].y==a[i+1].y && a[k].x>a[i+1].x) return false;
return true;
}
int main()
{
freopen("C.in","r",stdin);
int n,ans=0,l,r,u,d,i,j;
scanf("%d",&n);
for (int i=1;i<=n;i++)
scanf("%d%d",&a[i].x,&a[i].y);
sort(a+1,a+n+1,cmp);
int p=0,q=1;
F[0][0][0][0][0]=1;
for (int i=0;i<n;i++)
{
for (int j=1;j<=4;j++)
{
bz=true;
for (int k=1;k<=n;k++)
if (!pd(i,j,k)){bz=false;break;}
if (bz)
{
for (int l=0;l<=i;l++)
for (int r=0;r<=i;r++)
for (int u=0;u<=i;u++)
for (int d=0;d<=i;d++)
{
if (F[l][r][u][d][p])
{
if (j==1 && (a[l].y<a[i+1].y || !l))
F[l][r][Min(i+1,u)][d][q]=(F[l][r][Min(i+1,u)][d][q]+F[l][r][u][d][p])%mo;
if (j==2 && (a[r].y>a[i+1].y || !r))
F[l][r][u][Max(i+1,d)][q]=(F[l][r][u][Max(i+1,d)][q]+F[l][r][u][d][p])%mo;
if (j==3 && ((a[u].y>a[i+1].y && a[d].y<a[i+1].y) || (!u && !d) || (a[u].y>a[i+1].y && !d) || (a[d].y<a[i+1].y && !u)))
F[l][r][u][d][q]=(F[l][r][u][d][q]+F[l][r][u][d][p])%mo;
if (j==4)
F[Max(i+1,l)][Min(i+1,r)][u][d][q]=(F[Max(i+1,l)][Min(i+1,r)][u][d][q]+F[l][r][u][d][p])%mo;
}
}
}
}
p=(p+1)%mo;
q=(q+1)%mo;
for(int l=0;l<=n;l++)
for (int r=0;r<=n;r++)
for (int u=0;u<=n;u++)
for (int d=0;d<=n;d++)
F[l][r][u][d][q]=0;
}
for(int l=0;l<=n;l++)
for (int r=0;r<=n;r++)
for (int u=0;u<=n;u++)
for (int d=0;d<=n;d++)
ans=(ans+F[l][r][u][d][p])%mo;
printf("%d",ans);
}
代码颇长,打时需注意细节,哪怕一丁点也不能放过,因为这有可能会使你的程序整个WA掉(悲剧~TAT~),还有要注意一些地方的特判,以及转移DP方程,这需要不断地斟酌与思考
不得不说有些DP还真是我的软肋啊~在考场上很难想到像这种复杂DP,还需课后仔细思考~~