当时改题的时候没有意识到这个奇怪题意给出的性质,结果完全不会。
这个问题本身大致是一个不太可做的完美匹配计数,不过我们注意到对于每个
i
i
i,它能匹配到的
P
i
P_i
Pi是一个区间
[
L
i
,
R
i
]
[L_i,R_i]
[Li,Ri],且
L
L
L与
R
R
R均单调不增。
如果所有
i
i
i均有
L
i
=
0
L_i=0
Li=0,那么我们容易对
R
i
R_i
Ri排序后
O
(
N
)
\mathcal O(N)
O(N)计算。
考虑一个容斥的做法,我们枚举一些
L
i
>
0
L_i>0
Li>0的数,将它们的
R
i
R_i
Ri换为
L
i
−
1
L_i-1
Li−1,带上
−
1
-1
−1的权,再跑上面的做法,可惜复杂度是指数级的。
进一步的,我们发现有
L
0
<
R
N
−
1
L_0<R_{N-1}
L0<RN−1,且
∀
i
≥
N
\forall i\geq N
∀i≥N有
L
i
=
0
L_i=0
Li=0。那么可以取出所有的
R
i
R_i
Ri和
L
i
−
1
L_i-1
Li−1,从小到大排序后存在一个分界点,使得左侧为
L
0
−
1
L_0-1
L0−1到
L
N
−
1
−
1
L_{N-1}-1
LN−1−1与
R
N
R_{N}
RN到
R
2
N
−
1
R_{2N-1}
R2N−1,右侧为
R
0
R_0
R0到
R
N
−
1
R_{N-1}
RN−1。
这样就可以DP了。考虑枚举选了
k
k
k个
L
i
−
1
L_i-1
Li−1,我们在两侧同时DP,设
F
[
i
]
[
j
]
F[i][j]
F[i][j]表示考虑了左侧后
i
i
i个数以及这些数在右侧对应的后缀,目前左侧选了
j
j
j个
L
i
−
1
L_i-1
Li−1的方案数,因为我们假设了左侧最终会选
k
+
N
k+N
k+N个数,所以容易转移。
时间复杂度
O
(
N
3
)
\mathcal O(N^3)
O(N3)。
#include <bits/stdc++.h>
#define FR first
#define SE second
using namespace std;
typedef long long ll;
typedef pair<int,int> pr;
int MOD;
inline void add(int &x,ll y) {
x=(x+y)%MOD;
}
inline void sub(int &x,ll y) {
x=(x-y)%MOD;
}
pr num[505];
int f[2][255];
int main() {
int n;
scanf("%d%d",&n,&MOD);
int cnt=0;
for(int i=0;i<n;i++) {
int r1=0;
while (r1*r1+i*i<n*n) r1++;
int r2=r1;
while (r2<2*n-1&&(r2+1)*(r2+1)+i*i<=4*n*n) r2++;
num[++cnt]=pr(r1,r2+1);
}
for(int i=n;i<2*n;i++) {
int r=0;
while (r<2*n-1&&(r+1)*(r+1)+i*i<=4*n*n) r++;
num[++cnt]=pr(r+1,0);
}
sort(num+1,num+cnt+1);
int ans=0;
for(int i=0;i<=n;i++) {
int cur=0;
memset(f[cur],0,sizeof(f[cur]));
f[cur][0]=1;
int s1=0,s2=0;
for(int j=1;j<=cnt;j++) {
cur^=1;
memset(f[cur],0,sizeof(f[cur]));
if (!num[j].SE) {
int u=num[j].FR-s2,t=min(i,s1);
for(int k=0;k<=t;k++)
if (f[cur^1][k]) add(f[cur][k],(ll)f[cur^1][k]*(u-k));
s2++;
}
else {
int u=num[j].FR-s2,v=num[j].SE-i-n-s1,t=min(i,s1);
for(int k=0;k<=t;k++)
if (f[cur^1][k]) {
add(f[cur][k],(ll)f[cur^1][k]*(v+k));
sub(f[cur][k+1],(ll)f[cur^1][k]*(u-k));
}
s1++;
}
}
add(ans,f[cur][i]);
}
printf("%d\n",(ans+MOD)%MOD);
return 0;
}