题目链接
题目解法
神仙
d
p
dp
dp
考虑序列
c
i
=
m
i
n
(
a
i
,
a
i
−
1
)
c_i=min(a_i,a_{i-1})
ci=min(ai,ai−1),
c
1
=
a
1
,
c
n
+
1
=
a
n
c_1=a_1,c_{n+1}=a_n
c1=a1,cn+1=an,如果合法,必然满足
b
i
>
c
i
b_i>c_i
bi>ci
若将
c
i
c_i
ci 升序排列,
b
i
b_i
bi 升序排列,结果仍然成立
将
b
b
b 升序排列,
a
a
a 从小到大考虑
这里有一个神奇的连通块做法:
令
d
p
i
,
j
dp_{i,j}
dpi,j 为前
i
i
i 个
a
a
a,构成了
j
j
j 个连通块的方案数
考虑分 3 类情况:
- 将
i
+
1
i+1
i+1 加在一个连通块的左边或右边,显然
d
p
i
+
1
,
j
+
=
d
p
i
,
j
∗
2
j
dp_{i+1,j}+=dp_{i,j}*2j
dpi+1,j+=dpi,j∗2j
考虑限制条件,仅考虑加在右边的情况,那么加的位置的 c c c 为之前考虑过的数,现在只需要考虑后面一个数。注意到对于一个连通块,会影响到当前连通块及后一个元素,所以需要满足的条件为 a i + 1 < b i + j + 1 a_{i+1}<b_{i+j+1} ai+1<bi+j+1 - 将 i + 1 i+1 i+1 连接 2 个连通块, d p i + 1 , j − 1 + = d p i , j ∗ ( j − 1 ) dp_{i+1,j-1}+=dp_{i,j}*(j-1) dpi+1,j−1+=dpi,j∗(j−1),根据之前的思考方法,这里没有限制
- 将
i
+
1
i+1
i+1 新建 1 个连通块,
d
p
i
+
1
,
j
+
1
+
=
d
p
i
,
j
∗
(
j
+
1
)
dp_{i+1,j+1}+=dp_{i,j}*(j+1)
dpi+1,j+1+=dpi,j∗(j+1)
新建的 1 个可以影响到当前的位置与后一个位置,因为 b b b 从小到大排序过了,所以这里的限制为 a i < b i + j + 1 a_i<b_{i+j+1} ai<bi+j+1
时间复杂度 O ( n 2 ) O(n^2) O(n2)
#include <bits/stdc++.h>
using namespace std;
const int N=5100,P=998244353;
int n,a[N],b[N],dp[N][N];
inline int read(){
int FF=0,RR=1;
char ch=getchar();
for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
return FF*RR;
}
int main(){
n=read();
for(int i=1;i<=n;i++) a[i]=read();
for(int i=1;i<=n+1;i++) b[i]=read();
sort(b+1,b+n+2);
sort(a+1,a+n+1);
dp[0][0]=1;
for(int i=0;i<n;i++)
for(int j=0;j<=i;j++){
//放在1个连续段的左侧或右侧
if(a[i+1]<b[i+j+1]) dp[i+1][j]=(dp[i+1][j]+1ll*dp[i][j]*2*j)%P;
//连接2个连续段
if(j>1) dp[i+1][j-1]=(dp[i+1][j-1]+1ll*dp[i][j]*(j-1))%P;
//新建1个连续段
if(a[i+1]<b[i+j+1]) dp[i+1][j+1]=(dp[i+1][j+1]+1ll*dp[i][j]*(j+1))%P;
// cout<<i<<' '<<j<<" : "<<dp[i][j]<<'\n';
}
printf("%d",dp[n][1]);
return 0;
}