2020 CCPC 秦皇岛 H.Holy Sequence

我搬运我自己应该算原创吧

题目

题意

对于一个长度为 n n n的正整数数列 a n a_n an, 他合法的条件是:

  1. ∀ i ∈ [ 1 , n ] , a i ∈ 1 , 2 , … , n \forall i \in [1, n], a_i \in {1, 2, \dots, n} i[1,n],ai1,2,,n;
  2. ∀ i ∈ [ 1 , n ] , p i − p i − 1 ≤ 1 ( p k = m a x a 1 , a 2 , … , a k , p 0 = 0 ) \forall i \in [1, n], p_i - p_{i-1} \le 1 (p_k = max \\{ a_1, a_2, \dots, a_k \\}, p_0 = 0) i[1,n],pipi11(pk=maxa1,a2,,ak,p0=0).

对于每一个 t = 1 , 2 , … , n t = 1, 2, \dots, n t=1,2,,n, 求

∑ A ∈ S n c n t ( A , t ) 2 \sum_{A \in S_n}cnt(A, t)^2 ASncnt(A,t)2

其中, S n S_n Sn是所有长度为 n n n的合法序列的集合, c n t ( A , t ) cnt(A, t) cnt(A,t) t t t在数列 A A A中出现的次数.

答案对 m m m取模.

T T T组测试, 给出 n , m n, m n,m

1 ≤ T ≤ 10 , 1 ≤ n ≤ 3000 , 1 ≤ m ≤ 1 0 9 1 \le T \le 10, 1 \le n \le 3000, 1 \le m \le 10^9 1T10,1n3000,1m109

题解

感谢zzs, 舍友书名号, 大佬jyz, 我终于理解了这道数数题.

直接求显然不合理, 我们需要从平方的组合意义上"拆贡献".

首先考虑 x 2 x^2 x2代表什么. 比如小明有 x x x个数, 分别为 1 , 2 , … n 1, 2, \dots n 1,2,n, 小红也有同样的 x x x个数. 两人分别出一个数, 有序配对, 有多少中配对方法. 这个答案就是 x 2 x^2 x2.

这里只不过反过来, 把 x 2 x^2 x2拆成配对数, 如下:

c n t ( A , t ) 2 = ∣ ⟨ i , j ⟩ ∣ a i = a j = t , 1 ≤ i , j ≤ n ∣ cnt(A, t)^2 = \Big| \\{ \left \langle i, j \right \rangle \mid a_i = a_j = t, 1 \le i, j \le n \\} \Big| cnt(A,t)2=i,jai=aj=t,1i,jn

所以有:

KaTeX parse error: \cr valid only within a tabular/array environment

这算的是"所有合法序列 A A A中, 满足条件 a i = a j = t a_i = a_j = t ai=aj=t的有序对$\langle i, j \rangle 有 多 少 个 " . 可 以 转 化 成 " 对 于 满 足 条 件 有多少个". 可以转化成"对于满足条件 "."a_i = a_j = t 的 所 有 有 序 对 的所有有序对 \langle i, j \rangle , 有 多 少 个 合 法 序 列 , 有多少个合法序列 ,A$"

即:

KaTeX parse error: \cr valid only within a tabular/array environment

计算有序对数量时, 如果 i ≠ j i \ne j i=j, 那么有序对数量可以用无序对数量乘以 2 2 2表示; 如果 i = j i=j i=j, 有序无序是一样的东西. 所以, 上述式子进一步可以写成:

KaTeX parse error: \cr valid only within a tabular/array environment

先来看如何求 ∑ 1 ≤ i ≤ n ∣ A ∈ S n ∣ a i = t ∣ \sum_{1 \le i \le n} \Big| \\{ A \in S_n \mid a_i = t \\} \Big| 1inASnai=t.

枚举 i , a i = t i, a_i = t i,ai=t, 只要能够快速知道合法的前缀和后缀各有多少, 然后相乘即可. 可以用 d p dp dp求合法前缀和后缀. 设 f ( i , j ) f(i, j) f(i,j)为最大值为 j j j的长度为 i i i的合法序列数量, 方程:

f ( i , j ) = j ⋅ f ( i − 1 , j ) + f ( i − 1 , j − 1 ) f(i, j) = j \cdot f(i-1, j) + f(i-1, j-1) f(i,j)=jf(i1,j)+f(i1,j1)

解释如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zy7OwbJ4-1619673505287)(/img/vp-2020-ccpc-qinhuangdao-site/f.jpg)]

g ( i , j ) g(i, j) g(i,j)为(强行)** a 1 = j a_1 = j a1=j**时, 前缀最大值增加不超过 1 1 1, 长度为 i i i的合法序列数量.

为什么要这么定义呢? 因为已经枚举了 a i = t a_i = t ai=t, 后面的序列也满足前缀最大增加量不超过 1 1 1, 首先得把 t t t考虑进来, 然后还要考虑后面有多少个数. 这样的定义是没问题的. 难点在于方程转移:

g ( i , j ) = j ⋅ g ( i − 1 , j ) + g ( i − 1 , j + 1 ) g(i, j) = j \cdot g(i-1, j) + g(i-1, j+1) g(i,j)=jg(i1,j)+g(i1,j+1)

解释如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LYFfxG4f-1619673505289)(/img/vp-2020-ccpc-qinhuangdao-site/g.jpg)]

枚举 t t t第一次出现的位置 i i i, 选择两个 t t t配对, 有下面这些情况:

  1. 选的两个 t t t相同, 都是 a i a_i ai, 贡献为 f ( i − 1 , t − 1 ) ⋅ g ( n − i + 1 , t ) f(i-1, t-1) \cdot g(n - i + 1, t) f(i1,t1)g(ni+1,t)
  2. 选的两个 t t t不同, 第一个是 a i a_i ai, 第二个是后面某一个 a j = t a_j = t aj=t, 贡献为 2 ⋅ f ( i − 1 , t − 1 ) ⋅ ( n − i ) g ( n − i , t ) 2 \cdot f(i-1, t-1) \cdot (n-i)g(n-i, t) 2f(i1,t1)(ni)g(ni,t)(还是插入法, 在 n − 1 n-1 n1个元素的合法后缀之间插入一个 t t t, 就相当于"枚举"到了这个 a j = t a_j = t aj=t, 有序, 乘以 2 2 2)
  3. 选的两个 t t t相同, 是后面某一个 a j = t a_j = t aj=t, 贡献为 f ( i − 1 , t − 1 ) ⋅ ( n − 1 ) ⋅ g ( n − i , t ) f(i-1, t-1) \cdot (n-1) \cdot g(n-i, t) f(i1,t1)(n1)g(ni,t)
  4. 选的两个 t t t不同, 都在 i i i后面, 贡献为 2 ⋅ ( n − i 2 ) f ( i − 1 , t − 1 ) ⋅ g ( n − i − 1 , t ) 2 \cdot \tbinom{n-i}{2} f(i-1, t-1) \cdot g(n-i-1, t) 2(2ni)f(i1,t1)g(ni1,t)

注意到 a i = t a_i = t ai=t的一个必要条件是 t ≤ i t \le i ti, 在推 f f f g g g的时候需要注意.

边界条件很简单, 略.

还需注意, 在枚举 i i i的过程中, 如果后面的个数不足, 则不能转移.

复杂度 O ( n 2 ) O(n^2) O(n2)

{{% code %}}

const int maxn = 3e3+10;

int t, n, P;

LL Plus(LL a, LL b) {
	return a + b >= P ? (a + b) % P : a + b;
}

LL Mult(LL a, LL b) {
	return a * b >= P ? a * b % P : a * b;
}

LL f[maxn][maxn], g[maxn][maxn], ans[maxn];

int main() {
	scanf("%d", &t);
	for (int kase = 1; kase <= t; kase++) {
		scanf("%d%d", &n, &P);
		memset(f, 0, sizeof(f));
		memset(g, 0, sizeof(g));
		memset(ans, 0, sizeof(ans));
		f[0][0] = 1;
		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= i; j++)
				f[i][j] = Plus(f[i-1][j-1], Mult(j, f[i-1][j]));
		for (int j = 1; j <= n; j++)
			g[1][j] = 1;
		for (int i = 2; i <= n; i++)
			for (int j = (n - i + 1); j; j--)
				g[i][j] = Plus(g[i-1][j+1], Mult(j, g[i-1][j]));

		// 对每个t, 枚举其第一次出现的位置i, 由**题目**可知i >= t
		for (int t = 1; t <= n; t++)
			for (int i = t; i <= n; i++) {
				// 两次选i这个位置的t
				ans[t] = Plus(ans[t], Mult(f[i-1][t-1], g[n-i+1][t]));
				// 第一次选i这个位置的t, 第二次选后面某一个t
				// 由于有序, 所以乘以2
				if (i < n)
					ans[t] = Plus(ans[t], Mult(Mult(2, f[i-1][t-1]), Mult(n-i, g[n-i][t])));
				// 两次选后面同一个t
				if (i < n)
					ans[t] = Plus(ans[t], Mult(f[i-1][t-1], Mult(n-i, g[n-i][t])));
				// 两次选后面不同的t
				if (i + 1 < n)
					ans[t] = Plus(ans[t], Mult(Mult(f[i-1][t-1], n-i), Mult(n-i-1, g[n-i-1][t])));
			}
		printf("Case #%d:\n", kase);
		for (int t = 1; t <= n; t++)
			printf("%lld ", ans[t]);
		puts("");
	}
	return 0;
}

{{% /code %}}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值