题意
对于一个长度为 n n n的正整数数列 a n a_n an, 他合法的条件是:
- ∀ i ∈ [ 1 , n ] , a i ∈ 1 , 2 , … , n \forall i \in [1, n], a_i \in {1, 2, \dots, n} ∀i∈[1,n],ai∈1,2,…,n;
- ∀ i ∈ [ 1 , n ] , p i − p i − 1 ≤ 1 ( p k = m a x a 1 , a 2 , … , a k , p 0 = 0 ) \forall i \in [1, n], p_i - p_{i-1} \le 1 (p_k = max \\{ a_1, a_2, \dots, a_k \\}, p_0 = 0) ∀i∈[1,n],pi−pi−1≤1(pk=maxa1,a2,…,ak,p0=0).
对于每一个 t = 1 , 2 , … , n t = 1, 2, \dots, n t=1,2,…,n, 求
∑ A ∈ S n c n t ( A , t ) 2 \sum_{A \in S_n}cnt(A, t)^2 A∈Sn∑cnt(A,t)2
其中, S n S_n Sn是所有长度为 n n n的合法序列的集合, c n t ( A , t ) cnt(A, t) cnt(A,t)是 t t t在数列 A A A中出现的次数.
答案对 m m m取模.
T T T组测试, 给出 n , m n, m n,m
1 ≤ T ≤ 10 , 1 ≤ n ≤ 3000 , 1 ≤ m ≤ 1 0 9 1 \le T \le 10, 1 \le n \le 3000, 1 \le m \le 10^9 1≤T≤10,1≤n≤3000,1≤m≤109
题解
感谢zzs, 舍友书名号, 大佬jyz, 我终于理解了这道数数题.
直接求显然不合理, 我们需要从平方的组合意义上"拆贡献".
首先考虑 x 2 x^2 x2代表什么. 比如小明有 x x x个数, 分别为 1 , 2 , … n 1, 2, \dots n 1,2,…n, 小红也有同样的 x x x个数. 两人分别出一个数, 有序配对, 有多少中配对方法. 这个答案就是 x 2 x^2 x2.
这里只不过反过来, 把 x 2 x^2 x2拆成配对数, 如下:
c n t ( A , t ) 2 = ∣ ⟨ i , j ⟩ ∣ a i = a j = t , 1 ≤ i , j ≤ n ∣ cnt(A, t)^2 = \Big| \\{ \left \langle i, j \right \rangle \mid a_i = a_j = t, 1 \le i, j \le n \\} \Big| cnt(A,t)2=∣∣∣⟨i,j⟩∣ai=aj=t,1≤i,j≤n∣∣∣
所以有:
KaTeX parse error: \cr valid only within a tabular/array environment
这算的是"所有合法序列 A A A中, 满足条件 a i = a j = t a_i = a_j = t ai=aj=t的有序对$\langle i, j \rangle 有 多 少 个 " . 可 以 转 化 成 " 对 于 满 足 条 件 有多少个". 可以转化成"对于满足条件 有多少个".可以转化成"对于满足条件a_i = a_j = t 的 所 有 有 序 对 的所有有序对 的所有有序对\langle i, j \rangle , 有 多 少 个 合 法 序 列 , 有多少个合法序列 ,有多少个合法序列A$"
即:
KaTeX parse error: \cr valid only within a tabular/array environment
计算有序对数量时, 如果 i ≠ j i \ne j i=j, 那么有序对数量可以用无序对数量乘以 2 2 2表示; 如果 i = j i=j i=j, 有序无序是一样的东西. 所以, 上述式子进一步可以写成:
KaTeX parse error: \cr valid only within a tabular/array environment
先来看如何求 ∑ 1 ≤ i ≤ n ∣ A ∈ S n ∣ a i = t ∣ \sum_{1 \le i \le n} \Big| \\{ A \in S_n \mid a_i = t \\} \Big| ∑1≤i≤n∣∣∣A∈Sn∣ai=t∣∣∣.
枚举 i , a i = t i, a_i = t i,ai=t, 只要能够快速知道合法的前缀和后缀各有多少, 然后相乘即可. 可以用 d p dp dp求合法前缀和后缀. 设 f ( i , j ) f(i, j) f(i,j)为最大值为 j j j的长度为 i i i的合法序列数量, 方程:
f ( i , j ) = j ⋅ f ( i − 1 , j ) + f ( i − 1 , j − 1 ) f(i, j) = j \cdot f(i-1, j) + f(i-1, j-1) f(i,j)=j⋅f(i−1,j)+f(i−1,j−1)
解释如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zy7OwbJ4-1619673505287)(/img/vp-2020-ccpc-qinhuangdao-site/f.jpg)]
设 g ( i , j ) g(i, j) g(i,j)为(强行)** a 1 = j a_1 = j a1=j**时, 前缀最大值增加不超过 1 1 1, 长度为 i i i的合法序列数量.
为什么要这么定义呢? 因为已经枚举了 a i = t a_i = t ai=t, 后面的序列也满足前缀最大增加量不超过 1 1 1, 首先得把 t t t考虑进来, 然后还要考虑后面有多少个数. 这样的定义是没问题的. 难点在于方程转移:
g ( i , j ) = j ⋅ g ( i − 1 , j ) + g ( i − 1 , j + 1 ) g(i, j) = j \cdot g(i-1, j) + g(i-1, j+1) g(i,j)=j⋅g(i−1,j)+g(i−1,j+1)
解释如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LYFfxG4f-1619673505289)(/img/vp-2020-ccpc-qinhuangdao-site/g.jpg)]
枚举 t t t第一次出现的位置 i i i, 选择两个 t t t配对, 有下面这些情况:
- 选的两个 t t t相同, 都是 a i a_i ai, 贡献为 f ( i − 1 , t − 1 ) ⋅ g ( n − i + 1 , t ) f(i-1, t-1) \cdot g(n - i + 1, t) f(i−1,t−1)⋅g(n−i+1,t)
- 选的两个 t t t不同, 第一个是 a i a_i ai, 第二个是后面某一个 a j = t a_j = t aj=t, 贡献为 2 ⋅ f ( i − 1 , t − 1 ) ⋅ ( n − i ) g ( n − i , t ) 2 \cdot f(i-1, t-1) \cdot (n-i)g(n-i, t) 2⋅f(i−1,t−1)⋅(n−i)g(n−i,t)(还是插入法, 在 n − 1 n-1 n−1个元素的合法后缀之间插入一个 t t t, 就相当于"枚举"到了这个 a j = t a_j = t aj=t, 有序, 乘以 2 2 2)
- 选的两个 t t t相同, 是后面某一个 a j = t a_j = t aj=t, 贡献为 f ( i − 1 , t − 1 ) ⋅ ( n − 1 ) ⋅ g ( n − i , t ) f(i-1, t-1) \cdot (n-1) \cdot g(n-i, t) f(i−1,t−1)⋅(n−1)⋅g(n−i,t)
- 选的两个 t t t不同, 都在 i i i后面, 贡献为 2 ⋅ ( n − i 2 ) f ( i − 1 , t − 1 ) ⋅ g ( n − i − 1 , t ) 2 \cdot \tbinom{n-i}{2} f(i-1, t-1) \cdot g(n-i-1, t) 2⋅(2n−i)f(i−1,t−1)⋅g(n−i−1,t)
注意到 a i = t a_i = t ai=t的一个必要条件是 t ≤ i t \le i t≤i, 在推 f f f和 g g g的时候需要注意.
边界条件很简单, 略.
还需注意, 在枚举 i i i的过程中, 如果后面的个数不足, 则不能转移.
复杂度 O ( n 2 ) O(n^2) O(n2)
{{% code %}}
const int maxn = 3e3+10;
int t, n, P;
LL Plus(LL a, LL b) {
return a + b >= P ? (a + b) % P : a + b;
}
LL Mult(LL a, LL b) {
return a * b >= P ? a * b % P : a * b;
}
LL f[maxn][maxn], g[maxn][maxn], ans[maxn];
int main() {
scanf("%d", &t);
for (int kase = 1; kase <= t; kase++) {
scanf("%d%d", &n, &P);
memset(f, 0, sizeof(f));
memset(g, 0, sizeof(g));
memset(ans, 0, sizeof(ans));
f[0][0] = 1;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= i; j++)
f[i][j] = Plus(f[i-1][j-1], Mult(j, f[i-1][j]));
for (int j = 1; j <= n; j++)
g[1][j] = 1;
for (int i = 2; i <= n; i++)
for (int j = (n - i + 1); j; j--)
g[i][j] = Plus(g[i-1][j+1], Mult(j, g[i-1][j]));
// 对每个t, 枚举其第一次出现的位置i, 由**题目**可知i >= t
for (int t = 1; t <= n; t++)
for (int i = t; i <= n; i++) {
// 两次选i这个位置的t
ans[t] = Plus(ans[t], Mult(f[i-1][t-1], g[n-i+1][t]));
// 第一次选i这个位置的t, 第二次选后面某一个t
// 由于有序, 所以乘以2
if (i < n)
ans[t] = Plus(ans[t], Mult(Mult(2, f[i-1][t-1]), Mult(n-i, g[n-i][t])));
// 两次选后面同一个t
if (i < n)
ans[t] = Plus(ans[t], Mult(f[i-1][t-1], Mult(n-i, g[n-i][t])));
// 两次选后面不同的t
if (i + 1 < n)
ans[t] = Plus(ans[t], Mult(Mult(f[i-1][t-1], n-i), Mult(n-i-1, g[n-i-1][t])));
}
printf("Case #%d:\n", kase);
for (int t = 1; t <= n; t++)
printf("%lld ", ans[t]);
puts("");
}
return 0;
}
{{% /code %}}