CF1811G2 Vlad and the Nice Paths (hard version)

题目描述

这是该问题的困难版本,与简单版本的区别仅在于对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} cpmk+1=cpmk+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(1t104),表示测试用例的数量。

每个测试用例的第一行包含两个整数 n n n k ( 1 ≤ n , k ≤ 5000 ) k(1 \le n, k \le 5000) k(1n,k5000),表示瓷砖的数量和块的长度。

每个测试用例的第二行包含 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(1cin),表示瓷砖的颜色。

保证所有测试用例的 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 1345
  • 2 → 4 → 5 → 7 2 \to 4 \to 5 \to 7 2457
  • 1 → 3 → 5 → 7 1 \to 3 \to 5 \to 7 1357
  • 1 → 3 → 4 → 7 1 \to 3 \to 4 \to 7 1347

在第三个样例中,任何长度为 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 cntk,说明可以转移, f i = f j × C c n t − 1 k − 1 f_i = f_j \times C_{cnt - 1}^{k - 1} fi=fj×Ccnt1k1,然后同时更新一下方案数。

注意优美路径长度为 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值