已知 k ( 1 ≤ k ≤ 64 ) k(1\le k\le 64) k(1≤k≤64) ,对于每个仅包含 [ 0 , k − 1 ] [0,k-1] [0,k−1] 区间内的整数的数组,定义其优美度为非空子段之和为 k k k 的倍数的数量。
求有多少长度为 n ( 1 ≤ n ≤ 64 ) n(1\le n\le 64) n(1≤n≤64) 的数组,其优美度为 t ( 0 ≤ t ≤ n ( n + 1 ) 2 ) t(0\le t\le \frac{n(n+1)}{2}) t(0≤t≤2n(n+1)) ,答案对 998244353 998244353 998244353 取模。
先考虑,如何快速求一个数组
a
1
,
a
2
,
.
.
.
,
a
n
a_1,a_2,...,a_n
a1,a2,...,an 的优美度。
求区间和通常的技巧是前缀和,设
p
r
e
0
=
0
,
p
r
e
i
=
p
r
e
i
−
1
+
a
i
pre_0=0,pre_i=pre_{i-1}+a_i
pre0=0,prei=prei−1+ai ,有前缀和数组
p
r
e
0
,
p
r
e
1
,
.
.
.
p
r
e
n
pre_0,pre_1,...pre_n
pre0,pre1,...pren 。
对于一段区间
[
l
,
r
]
[l,r]
[l,r] ,其区间和为
p
r
e
r
−
p
r
e
l
−
1
pre_r-pre_{l-1}
prer−prel−1 ,若
p
r
e
r
−
p
r
e
l
−
1
≡
0
(
m
o
d
k
)
pre_r-pre_{l-1}\equiv 0(mod\ k)
prer−prel−1≡0(mod k) ,则
[
l
,
r
]
[l,r]
[l,r] 提供优美度。
不妨将
p
r
e
i
pre_i
prei 对
k
k
k 取模,此时若
p
r
e
l
−
1
=
p
r
e
r
pre_{l-1}=pre_r
prel−1=prer ,则
[
l
,
r
]
[l,r]
[l,r] 提供优美度。
设
c
n
t
i
cnt_i
cnti 表示
p
r
e
j
=
i
pre_j=i
prej=i 的数量,可得此时数组
a
a
a 的优美度为:
∑
i
=
0
k
−
1
C
c
n
t
i
2
\sum_{i=0}^{k-1}C_{cnt_i}^2
i=0∑k−1Ccnti2
(模 k k k 意义下)由前缀和数组差分可得到原数组,即前缀和数组与原数组一一对应,不妨对前缀和数组进行计数。
我们可以分别考虑每个
i
i
i 对应的
c
n
t
i
cnt_i
cnti 对优美度的贡献,然后用动态规划进行计数。
设
f
x
,
y
,
z
f_{x,y,z}
fx,y,z 表示已经考虑了
i
∈
[
0
,
x
]
i\in [0,x]
i∈[0,x] ,且
∑
i
=
0
x
c
n
t
i
=
y
\sum\limits_{i=0}^{x}cnt_i=y
i=0∑xcnti=y (已有
y
y
y 个数字),
∑
i
=
0
x
C
c
n
t
i
2
=
z
\sum\limits_{i=0}^{x}C_{cnt_i}^2=z
i=0∑xCcnti2=z (当前优美度为
z
z
z )的数组个数(此时不考虑剩下
n
−
y
n-y
n−y 个没有数字的空位)
枚举
c
n
t
x
cnt_x
cntx 的值,可得转移式如下(其中
C
n
−
(
y
−
i
)
i
C_{n-(y-i)}^i
Cn−(y−i)i 表示在除去原先的
y
−
i
y-i
y−i 已有数字的位置后,在剩下
n
−
(
y
−
i
)
n-(y-i)
n−(y−i) 个位置中插入
i
i
i 个
x
x
x 的方案数):
f
x
,
y
,
z
=
∑
i
=
0
y
f
x
−
1
,
y
−
i
,
z
−
C
i
2
∗
C
n
−
(
y
−
i
)
i
f_{x,y,z}=\sum_{i=0}^{y}f_{x-1,y-i,z-C_i^2}*C_{n-(y-i)}^i
fx,y,z=i=0∑yfx−1,y−i,z−Ci2∗Cn−(y−i)i
初始化时注意,有
y
y
y 个
0
0
0 时的优美度为
C
y
+
1
2
C_{y+1}^2
Cy+12 ,因为前缀和有
p
r
e
0
=
0
pre_0=0
pre0=0 。
注意到其中
x
x
x 维度在转移时只需上一维度,且
y
,
z
y,z
y,z 维度都从较小处转移过来,可以用类似背包的倒序枚举的技巧优化掉
x
x
x 维度。
本题时限较紧,注意常数优化。
#include
using namespace std;
template<class T>inline void read(T&x){
char c,last=' ';
while(!isdigit(c=getchar()))last=c;
x=c^48;
while(isdigit(c=getchar()))x=(x<<3)+(x<<1)+(c^48);
if(last=='-')x=-x;
}
const int MAXN=70,P=998244353;
int n,k,t;
int C[MAXN][MAXN];
int f[MAXN][MAXN*MAXN];
inline void add(int&a,int b){//快速取模加法
a+=b;
if(a>=P)a-=P;
}
void init(){//组合数打表
C[0][0]=1;
for(int i=1;i<MAXN;++i){
C[i][0]=1;
for(int j=1;j<=i;++j){
C[i][j]=C[i-1][j-1];
add(C[i][j],C[i-1][j]);
}
}
}
int main()
{
read(n),read(k),read(t);
init();
for(int y=0;y<=n;++y){
f[y][C[y+1][2]]=C[n][y];//初始化
}
for(int x=1;x<k;++x){
for(int y=n;y>=0;--y){//倒序枚举
for(int z=t;z>=0;--z){//倒序枚举
for(int i=1;i<=n;++i){//枚举数字x的数量
if(y-i>=0&&z-C[i][2]>=0)add(f[y][z],1ll*f[y-i][z-C[i][2]]*C[n-(y-i)][i]%P);
else break;//小剪枝
}
}
}
}
cout<<f[n][t]<<'\n';
return 0;
}