题目描述
这是该问题的困难版本,与简单版本的区别仅在于对n和k的约束条件不同。
Vlad
\texttt{Vlad}
Vlad 发现了一排
n
n
n 个瓷砖和整数
k
k
k。这些瓷砖从左到右编号,第
i
i
i 个瓷砖的颜色是
c
i
c_i
ci
。经过一番思考后,他决定要对其进行操作。
你可以从任意瓷砖开始,向右跳跃任意数量的瓷砖,形成路径 p p p。我们将长度为 m m m 的路径 p p p 称为优美路径,如果满足以下条件:
- p p p 可以被精确分割成长度为 k k k 的块,即 m m m 能被 k k k 整除;
- c p 1 = c p 2 = … = c p k c_{p_1} = c_{p_2} = \ldots = c_{p_k} cp1=cp2=…=cpk
- c p k + 1 = c p k + 2 = … = c p 2 k c_{p_{k + 1}} = c_{p_{k + 2}} = \ldots = c_{p_{2k}} cpk+1=cpk+2=…=cp2k
- … \ldots …
- c p m − k + 1 = c p m − k + 2 = … = c p m c_{p_{m - k + 1}} = c_{p_{m - k + 2}} = \ldots = c_{p_m} cpm−k+1=cpm−k+2=…=cpm
你的任务是找出最长优美路径的数量。由于这个数字可能非常大,请输出其对 1 0 9 + 7 10^9 + 7 109+7 取模的结果。
输入格式
第一行包含整数 t ( 1 ≤ t ≤ 1 0 4 ) t(1 \le t \le 10^4) t(1≤t≤104),表示测试用例的数量。
每个测试用例的第一行包含两个整数 n n n 和 k ( 1 ≤ n , k ≤ 5000 ) k(1 \le n, k \le 5000) k(1≤n,k≤5000),表示瓷砖的数量和块的长度。
每个测试用例的第二行包含 n n n 个整数 c 1 , c 2 , c 3 , … , c n ( 1 ≤ c i ≤ n ) c_1,c_2,c_3,\ldots,c_n(1 \le c_i \le n) c1,c2,c3,…,cn(1≤ci≤n),表示瓷砖的颜色。
保证所有测试用例的 n 2 n^2 n2 总和不超过 25 × 1 0 6 25 \times 10^6 25×106。
输出格式
输出 t t t 个数字,每个数字是对应测试用例的答案。优美路径的最大长度数量对 1 0 9 + 7 10^9+7 109+7 取模的结果。
样例
样例输入1:
5
5 2
1 2 3 4 5
7 2
1 3 1 3 3 1 3
11 4
1 1 1 1 1 1 1 1 1 1 1
5 2
1 1 2 2 2
5 1
1 2 3 4 5
样例输出1:
1
4
165
3
1
样例1解释:
在第一个样例中,无法构造出长度大于
0
0
0 的优美路径。
在第二个样例中,我们感兴趣的路径如下:
- 1 → 3 → 4 → 5 1 \to 3 \to 4 \to 5 1→3→4→5
- 2 → 4 → 5 → 7 2 \to 4 \to 5 \to 7 2→4→5→7
- 1 → 3 → 5 → 7 1 \to 3 \to 5 \to 7 1→3→5→7
- 1 → 3 → 4 → 7 1 \to 3 \to 4 \to 7 1→3→4→7
在第三个样例中,任何长度为 8 8 8 的路径都是优美的。
题解
由于题目中有从某个地方跳到右边,考虑进行 dp。
我原本想设 f i , j f_{i, j} fi,j 表示以 i i i 结尾的优美路径选了 j j j 个方案数,显然这样会超时。
定义两个 dp 数组, f i f_{i} fi 表示以 i i i 结尾的优美路径最长的长度, g i g_{i} gi表示以 i i i 结尾的优美路径最长的方案数。
正序枚举 i i i,倒序从 i i i 枚举 j j j,统计 a j = a i a_j = a_i aj=ai 的个数,如果 c n t ≥ k cnt \ge k cnt≥k,说明可以转移, f i = f j × C c n t − 1 k − 1 f_i = f_j \times C_{cnt - 1}^{k - 1} fi=fj×Ccnt−1k−1,然后同时更新一下方案数。
注意优美路径长度为 0 0 0 时方案数为 1 1 1,如果 WA#30 可能是你没开 long long。
#include<bits/stdc++.h>
using namespace std;
const int mod = 1e9 + 7;
int T, n, k;
int iv[5010][5010];
int f[5010];
long long g[5010];
int a[5010];
int main(){
//预处理组合数
for(int i = 0; i <= 5000; ++ i){
iv[i][0] = iv[i][i] = 1;
for(int j = 1; j < i; ++ j){
iv[i][j] = (iv[i - 1][j] + iv[i - 1][j - 1]) % mod;
}
}
scanf("%d", &T);
while(T --){
memset(f, 0, sizeof(f));
memset(g, 0, sizeof(g));
scanf("%d %d", &n, &k);
for(int i = 1; i <= n; ++ i){
scanf("%d", &a[i]);
}
g[0] = 1;
for(int i = 1; i <= n; ++ i){
int s = 1;
for(int j = i - 1; j >= 0; -- j){
if(s >= k){
if(f[j] + k > f[i]){
f[i] = f[j] + k;
g[i] = g[j] * iv[s - 1][k - 1] % mod;
}
else if(f[j] + k == f[i]){
g[i] += g[j] * iv[s - 1][k - 1] % mod;
g[i] %= mod;
}
}
if(a[i] == a[j]) ++ s;//注意由于转移是从f[j]转移的,因此不包括a[j]
}
}
int ans = 0;
long long ansp = 1;
for(int i = 1; i <= n; ++ i){
if(f[i] > ans) ans = f[i], ansp = g[i];
else if(f[i] == ans) ansp += g[i], ansp %= mod;
}
printf("%lld\n", ansp);
}
return 0;
}