题解 P7822【「RdOI R3」学习算法】

· · 题解

0.吐槽

赛事想出来的做法,当时觉得这题挺简单的(倒是代码实现上调了好久)

比完赛一看题解,??怎么个个都这么复杂,又是压维又是滚动数组的(甚至让我一度怀疑数据过水)

下面,我就介绍一种做法,理解起来很自然,优化起来也很容易。

1.题意解释

有一个长度为 n 的序列,其中包含的数字范围为从 1m.

规定数字 i 最多只能连续出现 a_i 次。

求满足上述要求的序列一共有多少中,答案对 10^9+7 取模。

2.解题思路

我们发现,这道题目的核心在于 “规定数字 i 最多只能连续出现 a_i 次” 这个限制条件。

我们不妨思考一下:如果我们按照线性动态规划的方式,一个一个地往表格里填数字,当我填到第 i 个数字的时候,和它关系最大的是什么?没错,是第 i-1 个数字。

如果第 i-1 个数字和第 i 个数字不一样的话,那么第 i 个数字填下去,一定是合法的。

否则呢?设当时填的数字为 j ,如果第 i-1 个数字和第 i 个数字是一样的,且 a_j> i-1 的话,我们仍然可以放心大胆地填下去,因为填下去之后,连续出现的数字是不可能超过 a_j 个的(因为本身就只有 a_j 个数字啊)。

那么,如果第 i-1 个数字和第 i 个数字是一样的,且 a_j\leq i-1 的时候呢?那么在前面 i-1 个数字构成的组合里,就不可避免地会出现一种情况,它由恰好 a_jj 结尾,当且仅当这种情况时,第 i 个数字不能填成 j.那么,我们怎么计算这种非法情况的方案数呢?

我们知道,在这种情况下,我们要填的数是第 i 个数,而且从第 i-a_j 个数到第 i-1 个数全部都是 j. 那么问题来了:第 i-a_j-1 个数是几呢?没错,只要不是 j 就行了!

而且我们还可以知道,这种情况下,第 i-a_j 个数到第 i-1 个数全都是固定死的,真正对非法方案数产生贡献的,只有前面的 i-a_j-1 个数。也就是说,“填到第 i-a_j-1 个数且不是以 j 结尾” 的方案数总和,等于 “填到第 i-1 个数,且从 i-a_ji-1 全部都是 j 的方案数”。好好思考一下上面这句话!

所以,第 i-1 个数字等于第 i 个数字时的合法方案总数

$=$ (前面 $i-1$ 个数字构成的合法方案总数) $-$ (前面 $i-a_j-1$ 个数字构成的不是以 $j$ 结尾的方案数总和). 现在我们知道了第 $i-1$ 个数字不等于第 $i$ 个数字时的合法方案总数,也知道了他们相等时的合法方案总数,那么把它们加起来,就知道了我们要求的方案数。 想到了以上这些,状态转移式子就很好推了。我们设 $dp[i][j]$ 表示当前填到第 $i$ 个数,且结尾数字是 $j$ 的合法方案总数,那么必然有: $$dp[i][j]=\sum_{k=1,k\not=j}^{m}dp[i-1][k]+(dp[i-1][j]-\sum_{k=1,k\not=j}^{m}dp[i-a[j]-1][k])$$ $$=\sum_{k=1}^{m}dp[i-1][k]-\sum_{k=1,k\not=j}^{m}dp[i-a[j]-1][k].$$ 这种做法是 $O(n^3)$ 的,拿不到满分,但我们可以很容易观察出,式子里面的 $\sum_{k=1}^{m}$ 是可以预处理出来的。 设 $sum[i]=\sum\limits_{k=1}^{m}dp[i][k]$ ,于是式子转化为: $$dp[i][j]=sum[i-1]-(sum[i-a[j]-1]-dp[i-a[j]-1][j]).$$ 转化成了 $O(n^2)$ 的,于是这道题就可做了。 至于滚动数组什么的?不需要!只要不用 ```long long``` 就可以卡过去。 另外在代码实现上,我们还需要注意之前说过的 $a_j>i-1$ 的情况,并予以特判。 还有一个地方需要特判:那就是当 $m=1$ 且唯一的 $a[1]<n$ 的时候,这种情况下是不可能填完的,应输出```0```。(对于其余的情况,可以证明是肯定存在解的,```12121212...```地填即可) *** ### 3.Code ```cpp #include <cstdio> typedef long long ll; #define MODNUM 1000000007 #define MAXN 7001 ll n, m; unsigned int dp[MAXN][MAXN]; ll sum[MAXN]; ll a[MAXN]; int main() { ll i, j, k; scanf("%lld%lld", &n, &m); for (i = 1; i <= m; i++) scanf("%lld", &a[i]); if (m == 1 && a[1] < n) { printf("0"); return 0; } for (i = 1; i <= m; i++) dp[1][i] = 1; sum[1] = m; for (i = 2; i <= n; i++) { for (j = 1; j <= m; j++) { dp[i][j] = sum[i - 1]; if (i > a[j]) { if (i == a[j] + 1) dp[i][j] = (dp[i][j] - 1 + MODNUM) % MODNUM; else dp[i][j] = (dp[i][j] - (sum[i - a[j] - 1] - dp[i - a[j] - 1][j]) + MODNUM) % MODNUM; } } for (j = 1; j <= m; j++) sum[i] = (sum[i] + dp[i][j]) % MODNUM; } printf("%lld", sum[n]); return 0; } ```