【CodeChef】【DP】Count Subsequences

CodeChef CSUBSQ Count Subsequences

题目大意

◇题目传送门◆

如果一个整数序列的各元素之和可以被给定整数 K K K 整数,则称这个序列是好的。给定整数序列 A 1 , A 2 , … , A N A_1, A_2, \ldots, A_N A1,A2,,AN。Hasan 想计算该序列有多少子序列是好的。不过这样的话问题就太简单了,因此 Hasan 决定再加一个限制。

任意非空下标序列 i 1 < i 2 < ⋯ < i L i_1< i_2 < \cdots < i_L i1<i2<<iL 对应子序列 A i 1 , A i 2 , … , A i L A_{i_1}, A_{i_2}, \ldots , A_{i_L} Ai1,Ai2,,AiL,如果满足 i L − i 1 ≥ W i_L - i_1 \ge W iLi1W 则称这个子序列非常好。请帮 Hasan 统计非常好的子序列方案数。由于答案可能非常大,请输出方案数对 1 0 9 + 7 10^9 + 7 109+7 取模的结果。

分析

假设没有 W W W 这个限制,那么这个问题就变得简单了,就是一个简单的背包问题。

我们如果先固定左端点 L L L,那么我们需要的就是在区间 [ L + W , N ] [L + W, N] [L+W,N] 中查询一个元素,使它与左端点 L L L 之间选出任意多个元素,它们的和模 K K K 等于 0 0 0

那么可以设计一个这样的状态:

状态 f ( L , i , j ) f(L, i, j) f(L,i,j) 表示当前区间左端点为 L L L (外层循环枚举),当前加入第 i i i 个数字,和模 K K K j j j 的方案数。

不难得出以下初始状态和转移:

初始状态: f ( L , L , 0 ) = 1 f(L, L, 0) = 1 f(L,L,0)=1 ∀ j ∈ [ 1 , K − 1 ] , f ( L , L , j ) = 0 \forall j \in [1, K - 1],f(L, L, j) = 0 j[1,K1],f(L,L,j)=0

转移: f ( L , i , j ) = f ( L , i − 1 , j ) + f ( L , i − 1 , ( j − A i + K ) m o d    K ) f(L, i, j) = f(L, i - 1, j) + f(L, i - 1, (j - A_i + K) \mod K) f(L,i,j)=f(L,i1,j)+f(L,i1,(jAi+K)modK)

显然最后的答案就是对于每个左端点 L L L,在区间 [ L + 1 , N ] [L + 1, N] [L+1,N] 中选择元素之和模 K K K 等于 0 0 0 的方案数 减掉 在区间 [ L + 1 , L + W − 1 ] [L + 1, L + W - 1] [L+1,L+W1] 中选择元素之和模 K K K 等于 0 0 0 的方案数。

显然直接采用这种方法枚举左端点 L L L 计算就可以超时。

那么考虑采用分治的方法进行优化。

设当前我们正在处理的区间为 [ L , R ] [L, R] [L,R],我们将它拆分成两个子区间 [ L , m i d ] [L, mid] [L,mid] [ m i d + 1 , R ] [mid + 1, R] [mid+1,R]。( m i d mid mid 是当前区间的中点),通过递归得到这两个子区间的答案。

那么我们就定义两个 DP 状态: f L ( p , k ) , f R ( p , k ) f_L(p, k), f_R(p, k) fL(p,k),fR(p,k),分别表示在区间 [ p , m i d ] [p, mid] [p,mid] 上有多少个序列的和模 K K K 等于 k k k,区间 [ m i d , p ] [mid, p] [mid,p] 上有多少个子序列的和模 K K K 等于 k k k

计算这两个 DP 状态的方法和上面介绍的一样。

那么考虑如何计算当前点的贡献。

我们记当前左边的元素之和模 K K K 等于 k l k_l kl,右边的元素之和模 K K K 等于 k r k_r kr。需要保证 k l + k r k_l + k_r kl+kr K K K 等于 0 0 0

那么我们就先枚举 [ L , m i d ] [L, mid] [L,mid] 的一个左端点 l l l,强制它要选(也就是它就是第一个)。那么如何计算这个如何计算,我们分成两种情况讨论:


情况1: 当我们所选取的 l + W − 1 ≥ m i d l + W - 1 \ge mid l+W1mid 时。

这时的答案就是必须选 l l l,在区间 [ l + 1 , m i d ] [l + 1, mid] [l+1,mid] 里任意选择的方案数乘上在区间 [ m i d + 1 , l + W − 1 ] [mid + 1, l + W - 1] [mid+1,l+W1] 随意选且在区间 [ l + W , R ] [l + W, R] [l+W,R] 选择至少一个的方案数。

用我们计算出的 DP 值表示就是:

( f L ( l , k l ) − f L ( l + 1 , k l ) ) × ( f R ( R , k r ) − f R ( l + W − 1 , k r ) ) (f_L(l, k_l) - f_L(l + 1, k_l)) \times (f_R(R, k_r) - f_R(l + W - 1, k_r)) (fL(l,kl)fL(l+1,kl))×(fR(R,kr)fR(l+W1,kr))

情况2: 当我们所选取的 l + W − 1 < m i d l + W - 1 < mid l+W1<mid 时。

我们发现无法直接计算出相应的答案,但我们可以将它拆成两部分:

  1. 必须选择 l l l,且在 [ m i d + 1 , R ] [mid + 1, R] [mid+1,R] 中选至少一个,在 [ l + 1 , m i d ] [l + 1, mid] [l+1,mid] 中随便选的方案数:可以利用我们计算的 DP 表示为 ( f L ( l , k l ) − f L ( l + 1 , k l ) ) × f R ( R , k r ) − f R ( m i d , k r ) (f_L(l, k_l) - f_L(l + 1, k_l)) \times f_R(R, k_r) - f_R(mid, k_r) (fL(l,kl)fL(l+1,kl))×fR(R,kr)fR(mid,kr)
  2. 必须选择 l l l,且在 [ m i d + 1 , R ] [mid + 1, R] [mid+1,R] 中不选,在 [ l + W , m i d ] [l + W, mid] [l+W,mid] 中选至少一个,在 [ l + 1 , l + W − 1 ] [l + 1, l + W - 1] [l+1,l+W1] 中随便选的方案数,这个我们采用递归的方法进行处理。

总时间复杂度为 O ( N K log ⁡ 2 N ) O(NK\log_2 N) O(NKlog2N)

参考代码

#include <cstdio>
#include <algorithm>
using namespace std;

typedef long long ll;
const int Maxn = 1e5;
const int Maxk = 50;
const ll Mod = 1e9 + 7;

int N, K, W;
int A[Maxn + 5];

ll fLef[Maxn + 5][Maxk + 5], fRig[Maxn + 5][Maxk + 5];
ll ans;

void Divide(int lb, int ub) {
	if(ub - lb < W) return;
	if(lb == ub) {
		if(A[lb] == 0) ans = (ans + 1) % Mod;
		return;
	}
	int mid = (lb + ub) >> 1;
	Divide(lb, mid), Divide(mid + 1, ub);
	for(int k = 0; k < K; k++)
		fRig[mid][k] = fLef[mid + 1][k] = 0;
	fRig[mid][0] = 1;
	for(int j = mid + 1; j <= ub; j++)
		for(int k = 0; k < K; k++)
			fRig[j][k] = (fRig[j - 1][k] +
				fRig[j - 1][(k - A[j] + K) % K]) % Mod;
	fLef[mid + 1][0] = 1;
	for(int j = mid; j >= lb; j--)
		for(int k = 0; k < K; k++)
			fLef[j][k] = (fLef[j + 1][k] +
				fLef[j + 1][(k - A[j] + K) % K]) % Mod;
	ll tot = 0;
	for(int lefsum = 0; lefsum < K; lefsum++) {
		int rigsum = (K - lefsum + K) % K;
		for(int j = lb; j + W <= ub && j <= mid; j++) {
			ll lef = (fLef[j][lefsum] - fLef[j + 1][lefsum] + Mod) % Mod, rig;
			if(j + W - 1 >= mid)
				rig = (fRig[ub][rigsum] - fRig[j + W - 1][rigsum] + Mod) % Mod;
			else rig = (fRig[ub][rigsum] - fRig[mid][rigsum] + Mod) % Mod;
			tot = (tot + lef * rig % Mod) % Mod;
		}
	}
	ans = (ans + tot) % Mod;
}

int main() {
#ifdef LOACL
	freopen("in.txt", "r", stdin);
	freopen("out.txt", "w", stdout);
#endif
	int _;
	scanf("%d", &_);
	while(_--) {
		scanf("%d %d %d", &N, &K, &W);
		for(int i = 1; i <= N; i++) scanf("%d", &A[i]);
		ans = 0;
		Divide(1, N);
		printf("%lld\n", ans);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值