把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

CF140E New Year Garland 题目分析

CF140E New Year Garland 题目分析

挺不错的动态规划题目。

思路

一看到题目便可以知道每一层和层与层之间是要分开来算的(这种类似的动态规划还有很多)。

我们先看看层与层之间的。

层与层

题目要求:相邻的两层的小球颜色集合不相同

那么区分相邻两层小球颜色集合不同可以通过数量不同来区分,然后再进行讨论即可。

根据上述,显然地,设 \(f_{i,j}\) 表示已经完成前 \(i\) 层,到了第 \(i\) 层小球颜色集合的数量为 \(j\) 的总方案。

我们先抛开重不重复不谈,那么它的总方案肯定是从前面的那一层转移过来,即 \(f_{i-1,k}\),其中 \(k\in [1,l_{i-1}]\)

那么是不是就是

\[f_{i,j}=\sum_{k=1}^{l_{i-1}}f_{i-1,k} \]

呢?显然不是,这里的 \(j\) 是数量,并不是选了什么,因此还要有 \(C_m^j\) 来确定选 \(j\) 种颜色球的方案(这是对于当前 \(i\) 的)。

每一层

我们还需要当前这一层的贡献,而每一层的贡献求法类似,根据我们的总状态可以设 \(g_{i,j}\) 表示长度为 \(i\) 的位置给你放 \(j\) 种颜色(此处颜色确定,并且按从大到小的顺序排列)的球的放的方案是多少。

转移也是显然的:

\[g_{i,j}=g_{i-1,j-1}+g_{i-1,j}\times (j-1) \]

后面之所以要乘上 \((j-1)\) 是因为有 \(j\) 种颜色,并且不能与上一个相等,故为 \((j-1)\) 个。

合起来!

我们可以预先求出 \(g_{i,j}\),因此这个处理的复杂度为 \(\mathcal{O}((\max l_i)^2)\)

根据上述,我们不难把 \(f_{i,j}\)(不考虑层与层之间的要求)的求法合并为:

\[f_{i,j}=C_{m}^j\times g_{i,j}\times j! \times \sum_{k=1}^{l_{i-1}}f_{i-1,k} \]

实际上我们的每一层方案不一定是要从小到大排列的,因此我们乘上 \(j!\) 来保证每一种可能(这个可以理解为将这 \(j\) 个不同颜色原本按照一个一个编号排着,想通颜色的是同一个标号,然后你决定要将每个编号的每种可能都取到,这样就要乘上 \(j!\)),还不理解可以参考下述:

按照原本的转移我得到的所有可能情况是 \(\{a_i\}\)

比如说,我有 \(j\) 种颜色,将它们依次标号为:\(\{1,2,3,\dots,j\}\)

实际上我可以是:\(\{2,1,j,\dots,4\}\),或者是 \(\{4,1,j-1,\dots,j\}\) 等等都有可能。

但是这所有的情况总和便是 \(j!\)。触类旁通,枚举所有可能全排列(长度为 \(n\))的时间复杂度就是 \(\mathcal{O}(n!)\)

如果你还是不能理解就可以用数学的思想考虑:

总共有 \(j\) 个位置,我填在第 \(1\) 位的方案为 \(j\),第 \(2\) 位的为 \(j-1\)

易有:第 \(i\) 位的方案为 \(j-i\)

最后填完 \(j\) 个位置的方案为 \(j!\)

由于模数 \(p\) 不一定是质数,这就导致我们的 \(C_{m}^j\) 不能用逆元求解,但是我们发现:

\[P_{m}^j=C_{m}^j\times j! \]

于是合并一下:

\[f_{i,j}=P_m^j\times g_{i,j}\times \sum_{k=1}^{l_{i-1}}f_{i-1,k} \]

真是棒极了!现在我们考虑与上一层不同的情况,我们发现两层颜色集合相等的两种情况的充分条件是 \(k=j\)

然后我们又发现,两者颜色集合完全相等在 \(C_{m}^j\) 种只有一种情况。

故我们可以得到:

\[f_{i,j}=P_m^j\times g_{i,j}\times \sum_{k=1}^{l_{i-1}}f_{i-1,k}-f_{i-1,j}\times g_{i,j}\times j! \]

这个乘上 \(j!\) 与上述同理。

一些提示(思考后再看)

我给出两个提示:一是怎么求 \(P_{m}^j\),二是怎么优化。

有人说:

\[P_{m}^j=\frac{m!}{(m-j)!} \]

这不还是要逆元吗?

但我说:

\[P_m^j=\prod_{i=m-j+1}^mi \]

而显然:

\[P_m^0=m \]

然后从 \(i-1\) 做到 \(i\) 不就行了,每次只多乘上一个数。

如何优化?

其实很简单。

我们发现:\(\sum_{k=1}^{l_{i-1}}f_{i-1,k}\) 其实跟 \(j\) 没有任何关系,直接在枚举 \(i\) 的时候加一下不就完了。

代码

\(L=\max l_i\),总时间复杂度为 \(\mathcal{O}(L^2+\sum l_i)\)

代码如下:

#include <iostream>
#include <cstdio>
#include <stdlib.h>
#include <algorithm>
#include <cstring>
#include <vector>
#define int long long
#define N 1000006
#define M 5005
using namespace std;
int n,m,mod,l[N],g[M][M],mxm,p[N],f[2][M],jc[M];
signed main(){
	cin >> n >> m >> mod;
	for (int i = 1;i <= n;i ++) cin >> l[i],mxm = max(mxm,l[i]);
	jc[0] = 1;
	for (int i = 1;i <= mxm;i ++) jc[i] = jc[i - 1] * i % mod;
	g[1][1] = 1;
	for (int i = 2;i <= mxm;i ++)
		for (int j = 1;j <= min(i,m);j ++)
			g[i][j] = (g[i - 1][j - 1] + g[i - 1][j] * (j - 1) % mod) % mod;
//	cout << g[2][2] << ' ' << p[2] << ' ' << p[1] << endl;
	p[0] = 1;
	for (int i = 1;i <= mxm;i ++)
		p[i] = p[i - 1] * (m - i + 1) % mod;
	for (int j = 1;j <= min(l[1],m);j ++)
		f[1][j] = g[l[1]][j] * p[j] % mod;
	for (int i = 2;i <= n;i ++) {
		int sum = 0;
		for (int k = 1;k <= min(m,l[i - 1]);k ++)
			sum = (sum + f[i - 1 & 1][k]) % mod;
//		cout << sum << endl;
		for (int j = 1;j <= min(m,l[i]);j ++) {
			f[i & 1][j] = sum * g[l[i]][j] % mod * p[j] % mod;
			if (j <= l[i - 1]) f[i & 1][j] = (f[i & 1][j] - f[i - 1 & 1][j] * g[l[i]][j] % mod * jc[j] % mod + 5 * mod) % mod;
//			cout << f[i & 1][j] << endl;
		}
	}
	int ans = 0;
	for (int j = 1;j <= l[n];j ++)
		ans = (ans + f[n & 1][j]) % mod;
	cout << ans;
	return 0;
}

提示一个很容易错的点:直接算 \(f_1\) 的时候记得取模,不然大数据过不了

posted @ 2025-03-11 19:17  high_skyy  阅读(19)  评论(0)    收藏  举报
浏览器标题切换
浏览器标题切换end