JZOJ 4228. 【五校联考3day2】C
题目
Description
在远古的YL国大地上,有
n
n
n个祭坛,每个祭坛上四个方向写有“艄、毜、鼛、瓯”四个大字,其实这在YL国古代分别是“东、南、西、北”的意思。
YL国每年都要举行祈福消灾的祭祀活动,这个时候,每个祭坛都要在艄毜鼛瓯四个方向中选一个方向,祭坛将向这个方向发出一道隐形的光线,如果两个祭坛发出的光线相交,或者祭坛发出的光线经过了别的祭坛,则祭祀不仅不能成功还将遭到上天的惩罚,如果这些条件都满足则祭祀将成功,YL国在接下来的一年内将不会有任何灾难,甚至只会有人出生不会有人死亡。
抽象的来说,如果我们以“艄”方向为
x
x
x轴,“瓯”方向为
y
y
y轴,建立笛卡尔坐标系,那么每个祭坛将会对应一个整点。每个点向上下左右四个方向之一连出一条射线,这些射线不能相交且射线不能经过除了发出点之外的其他点。
现在他们又到了祭祀活动的时候,他们想知道,有多少种方法选择每个祭坛的方向,使得自己的祭祀能成功?输出方案数对
998244353
998244353
998244353取模后的值。
Input
第一行一个正整数
n
n
n。
接下来
n
n
n行,第
i
+
1
i + 1
i+1行两个整数
x
i
x_i
xi,
y
i
y_i
yi,表示第i个祭坛在题目中所述的坐标系下的坐标为
(
x
i
,
y
i
)
(x_i, y_i)
(xi,yi)。
Output
输出一行一个整数,表示要求的方案数对 998244353 998244353 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
解释:只有一个祭坛,显然四个方向都可以发射。
输出2:
14
解释:
对于所有的
4
×
4
=
16
4 × 4 = 16
4×4=16种情况中,只有两种不可行:
1
1
1号向上,
2
2
2号向左;
1
1
1号向右,
2
2
2号向下。
输出3:
144
解释:
最上面的祭坛可以向左中右三个方向连出射线,最下面的祭坛可以向右下左三个方向连出射线,中间
4
4
4个祭坛可以向左右连出射线,方案数为
3
×
2
×
2
×
2
×
2
×
3
=
144
3 × 2 × 2 × 2 × 2 × 3 = 144
3×2×2×2×2×3=144.
输出4:
117
解释:
祭坛的位置如图所示:
输出5:
24341
Data Constraint
对于前30%的数据,
n
≤
9
n ≤ 9
n≤9.
对于前40%的数据,
n
≤
18
n ≤ 18
n≤18.
对于前60%的数据,
n
≤
36
n ≤ 36
n≤36.
对于前100%的数据,
n
≤
54
n ≤ 54
n≤54.
对于所有
i
,
j
i, j
i,j,有
x
i
≠
x
j
x_i ≠ x_j
xi�=xj或
y
i
≠
y
j
y_i ≠ y_j
yi�=yj,且
∣
x
i
∣
,
∣
y
i
∣
≤
1
0
9
|x_i|, |y_i| ≤ 10^ 9
∣xi∣,∣yi∣≤109.
题解
- 题目很好理解,也不难可以简化为:
- 以给定的 n n n个点为端点,每个点可以往上、下、左、右四个方向之一作射线,使得它们互不相交。求总的方案数。
- 观察数据限制,发现 n n n很小,
- 最早想到状压 D P DP DP,很容易发现压不下所有状态。。。
- 但应该自然想到是一个 D P DP DP题,那应该怎么实现呢?
- 不妨先把坐标离散化,
- 再给它们排序,
- 以 y y y坐标从大到小为第一关键字,以 x x x坐标从小到大为第二关键字,
- 直观地说就是从上到下,从左到右。
- 先不考虑怎么设状态,分析一下题目条件!!!
- 按顺序处理每个点,四个方向都要转移,
- 会有限制条件,想想有哪些限制?
-
一、经过其他的点
- 这个很好实现,因为数据很小, n 2 n^2 n2预处理出来是否存在有点在某个点的上、下、左、右即可。
- 必须先满足条件一不成立,再去判断条件二。
-
二、与其他射线相交
- 这是这道题目的核心——
- 1、向下作射线
- 如图,实心的表示当前做到的点,空心的表示已经做过的点,
- 因为是从上往下作的,所以上面的无论怎么作也不会受到影响。
- 2、向上作射线
- 如图,实心的表示当前做到的点,空心的表示已经做过的点,
- 如果存在有一个点,在当前点左边向右作了射线,或,在当前点右边向左作了射线,都不符合条件。
- 否则就可以向上作射线。
- 总不能上面一个个点判断吧!
- 实际上,如果上面最左边的一个向右作的点(重点1)在当前点的右边,最右边一个向左作的点(重点2)在当前点的左边,那就符合条件。
- 3、向左作射线
- 如图,实心的表示当前做到的点,空心的表示已经做过的点,
- 如果上面有向下作射线的点在当前点的左边,那就不符合条件。
- 当然,不能一个个判断。
- 只要最左边的一个向下作的点(重点3)在当前点的右边,那就符合条件。
- 4、向右作射线
- 同3.
- 只要最右边的一个向下作的点(重点4)在当前点的左边,那就符合条件。
- ————————————————————————————————————————————————
- 可以开始设状态了!!!
- 注意到上面标出的四个重点了吗?用它们来设状态就好了!
- 设 f [ i ] [ j ] [ k ] [ l ] [ h ] f[i][j][k][l][h] f[i][j][k][l][h]表示当前做到第 i i i个点, j , k , l , h j,k,l,h j,k,l,h分别为四个重点的方案数。
- 每次枚举状态,去更新其他状态。
- 假设当前枚举到 i i i,通过 j , k , l , h j,k,l,h j,k,l,h和第 i + 1 i+1 i+1个点的横纵坐标比较,
- 即可得出当前状态下第 i + 1 i+1 i+1个点是否可以分别往四个方向作射线,
- 如果可以,记得根据状态转移,转移后的 j , k , l , h j,k,l,h j,k,l,h会有改变:
- (如当前向下作,且当前点在 l l l的左边,那么就得用 f [ i ] [ j ] [ k ] [ l ] [ h ] f[i][j][k][l][h] f[i][j][k][l][h]更新 f [ i + 1 ] [ j ] [ k ] [ x [ i + 1 ] ] [ h ] f[i+1][j][k][x[i+1]][h] f[i+1][j][k][x[i+1]][h],其他类似)
- 发现空间不够,那么将第一维设置成 0 / 1 0/1 0/1滚动。
代码
#include<cstdio>
#include<cstring>
using namespace std;
#define md 998244353
struct node
{
int x,y,p,q;
}a[60];
int xx[56],yy[56],le[56],ri[56],up[56],dn[56];
int f[2][56][56][56][56],g[2][56][56][56][56];
int main()
{
int n,i,j,k,l,h;
scanf("%d",&n);
for(i=1;i<=n;i++)
{
scanf("%d%d",&a[i].p,&a[i].q);
xx[i]=a[i].p,yy[i]=a[i].q;
}
for(i=1;i<n;i++)
for(j=i+1;j<=n;j++) if(xx[i]>xx[j]) xx[0]=xx[i],xx[i]=xx[j],xx[j]=xx[0];
for(i=1;i<n;i++)
for(j=i+1;j<=n;j++) if(yy[i]>yy[j]) yy[0]=yy[i],yy[i]=yy[j],yy[j]=yy[0];
int ln=0,ln1=0;
xx[0]=yy[0]=-2147483647;
for(i=1;i<=n;i++)
{
if(xx[i]!=xx[i-1]) ln++;
for(j=1;j<=n;j++) if(a[j].p==xx[i]) a[j].x=ln;
if(yy[i]!=yy[i-1]) ln1++;
for(j=1;j<=n;j++) if(a[j].q==yy[i]) a[j].y=ln1;
}
for(i=1;i<n;i++)
for(j=i+1;j<=n;j++)
if(a[i].y<a[j].y||(a[i].y==a[j].y&&a[i].x>a[j].x)) a[0]=a[i],a[i]=a[j],a[j]=a[0];
for(i=1;i<=n;i++)
{
dn[i]=up[i]=le[i]=ri[i]=0;
for(j=1;j<=n;j++) if(a[j].x==a[i].x&&a[j].y<a[i].y) dn[i]=1;
for(j=1;j<=n;j++) if(a[j].x==a[i].x&&a[j].y>a[i].y) up[i]=1;
for(j=1;j<=n;j++) if(a[j].y==a[i].y&&a[j].x>a[i].x) ri[i]=1;
for(j=1;j<=n;j++) if(a[j].y==a[i].y&&a[j].x<a[i].x) le[i]=1;
}
memset(g,127,sizeof(g));
g[0][ln+1][0][ln+1][0]=0;
f[0][ln+1][0][ln+1][0]=1;
for(i=0;i<n;i++)
for(j=0;j<=ln+1;j++)
for(k=0;k<=ln+1;k++)
for(l=0;l<=ln+1;l++)
for(h=0;h<=ln+1;h++) if(g[i%2][j][k][l][h]==i)
{
if(!dn[i+1])//down
{
int min=l,max=h;
if(a[i+1].x<min) min=a[i+1].x;
if(a[i+1].x>max) max=a[i+1].x;
if(g[1-i%2][j][k][min][max]!=i+1)
{
g[1-i%2][j][k][min][max]=i+1;
f[1-i%2][j][k][min][max]=f[i%2][j][k][l][h];
}
else f[1-i%2][j][k][min][max]+=f[i%2][j][k][l][h],f[1-i%2][j][k][min][max]%=md;
}
if(!up[i+1]&&j>a[i+1].x&&k<a[i+1].x)//up
{
if(g[1-i%2][j][k][l][h]!=i+1)
{
g[1-i%2][j][k][l][h]=i+1;
f[1-i%2][j][k][l][h]=f[i%2][j][k][l][h];
}
else f[1-i%2][j][k][l][h]+=f[i%2][j][k][l][h],f[1-i%2][j][k][l][h]%=md;
}
if(!le[i+1]&&l>a[i+1].x)//left
{
int max=k;
if(a[i+1].x>max) max=a[i+1].x;
if(g[1-i%2][j][max][l][h]!=i+1)
{
g[1-i%2][j][max][l][h]=i+1;
f[1-i%2][j][max][l][h]=f[i%2][j][k][l][h];
}
else f[1-i%2][j][max][l][h]+=f[i%2][j][k][l][h],f[1-i%2][j][max][l][h]%=md;
}
if(!ri[i+1]&&h<a[i+1].x)//right
{
int min=j;
if(a[i+1].x<min) min=a[i+1].x;
if(g[1-i%2][min][k][l][h]!=i+1)
{
g[1-i%2][min][k][l][h]=i+1;
f[1-i%2][min][k][l][h]=f[i%2][j][k][l][h];
}
else f[1-i%2][min][k][l][h]+=f[i%2][j][k][l][h],f[1-i%2][min][k][l][h]%=md;
}
}
int ans=0;
for(i=0;i<=ln+1;i++)
for(j=0;j<=ln+1;j++)
for(k=0;k<=ln+1;k++)
for(l=0;l<=ln+1;l++) if(g[n%2][i][j][k][l]==n) ans=(ans+f[n%2][i][j][k][l])%md;
printf("%d",ans);
return 0;
}