[日常训练] 传统题


Solution

  • 一道组合计数好题,orz zzq。
  • 对原问题进行转化,答案 =∑i=1nf(ans=i)×i=∑i=1nf(ans≥i)=∑i=1n(mn−f(ans&lt;i))=n×mn−∑i=0n−1f(ans≤i)(f为方案数)= \sum \limits_{i = 1}^{n} f(ans = i) \times i = \sum \limits_{i = 1}^{n} f(ans \ge i) = \sum \limits_{i = 1}^{n}(m ^ n - f(ans &lt; i)) = n \times m^n - \sum \limits_{i = 0}^{n - 1}f(ans \le i)(f为方案数)=i=1nf(ans=i)×i=i=1nf(ansi)=i=1n(mnf(ans<i))=n×mni=0n1f(ansi)(f)
  • 考虑如何计算 ∑i=0n−1f(ans≤i)\sum \limits_{i = 0}^{n - 1}f(ans \le i)i=0n1f(ansi)
  • 先把序列分成 jjj 个块,相邻的块颜色不同,染色的方案数为 m(m−1)j−1m(m - 1)^{j - 1}m(m1)j1
  • 计算分成 jjj 个块的方案要考虑 ans≤ians \le iansi 的限制,直接计算并不容易,我们通过容斥,枚举 kkk 个长度 &gt;i&gt; i>i 的块,把它们的长度减去 iii,剩下的部分就可以直接用隔板法计算。
  • 因此 ∑i=0n−1f(ans≤i)=∑i=0n−1∑j=1nm(m−1)j−1∑k=0j(−1)kCjkCn−ik−1j−1\sum \limits_{i = 0}^{n - 1} f(ans \le i) = \sum \limits_{i = 0}^{n - 1} \sum \limits_{j = 1}^{n}m(m - 1)^{j - 1} \sum \limits_{k = 0}^{j} (-1)^kC_{j}^{k}C_{n - ik - 1}^{j - 1}i=0n1f(ansi)=i=0n1j=1nm(m1)j1k=0j(1)kCjkCnik1j1
  • 考虑简化这个式子,把无关项外移,先枚举 kkk 再枚举 jjj,得到=m∑i=0n−1∑k=0n(−1)k∑j=max⁡{k,1}n(m−1)j−1CjkCn−ik−1j−1=m\sum \limits_{i = 0}^{n - 1}\sum\limits_{k = 0}^{n}(-1)^k\sum \limits_{j = \max\{k, 1\}}^{n}(m - 1)^{j - 1}C_{j}^{k}C_{n - ik - 1}^{j - 1}=mi=0n1k=0n(1)kj=max{k,1}n(m1)j1CjkCnik1j1
  • 考虑组合数的限制 k−1≤j−1≤n−ik−1k - 1 \le j - 1 \le n - ik - 1k1j1nik1,所以 k≤n−ik,(i+1)k≤nk \le n - ik, (i + 1)k \le nknik,(i+1)kn,枚举 i,ki, ki,k 的复杂度为调和级数。
  • 现在只要能够快速计算枚举 jjj 的部分,问题就能解决了。
  • 先做一些简单的变换:Cn−ik−1j−1=(n−ik−1)!(j−1)!(n−ik−j)!=jn−ik×(n−ik)!j!×(n−ik−j)!=jn−ikCn−ikjC_{n - ik - 1}^{j - 1} = \frac{(n - ik - 1)!}{(j - 1)!(n - ik - j)!} = \frac{j}{n - ik} \times \frac{(n - ik)!}{j! \times (n - ik - j)!} = \frac{j}{n - ik}C_{n - ik}^{j}Cnik1j1=(j1)!(nikj)!(nik1)!=nikj×j!×(nikj)!(nik)!=nikjCnikj
  • 代入原式,得到 =m∑i=0n−1∑k=0n(−1)kn−ik∑j=max⁡{k,1}nj(m−1)j−1CjkCn−ikj=m\sum \limits_{i = 0}^{n - 1}\sum\limits_{k = 0}^{n}\frac{(-1)^k}{n - ik}\sum \limits_{j = \max\{k, 1\}}^{n}j(m - 1)^{j - 1}C_{j}^{k}C_{n - ik}^{j}=mi=0n1k=0nnik(1)kj=max{k,1}nj(m1)j1CjkCnikj
  • 考虑寻找枚举 jjj 部分的组合意义:
  1. n−ikn - iknik 个点中选 jjj 个点(Cn−ikjC_{n - ik}^{j}Cnikj);
  2. jjj 个点中选 kkk 个点(CjkC_{j}^{k}Cjk);
  3. jjj 个点中选一个关键点(jjj);
  4. jjj 个点中除关键点外的点用 m−1m - 1m1 种颜色染色((m−1)j−1(m - 1)^{j - 1}(m1)j1)。
  • 注意到若该组合意义下存在方案,要满足 jjj 至少为 1。
  • 考虑先枚举选 kkk 个点:
  1. n−ikn - iknik 个点中选 kkk 个点 (Cn−ikkC_{n - ik}^{k}Cnikk);
  2. 分两种情况讨论:
    1. 关键点不在 kkk 个点之中:
      [1] 将 kkk 个点用 m−1m - 1m1 种颜色染色((m−1)k(m - 1)^{k}(m1)k);
      [2] 在 n−ik−kn - ik - knikk 个点中选一个关键点(n−ik−kn - ik - knikk);
      [3] 在剩下的 n−ik−k−1n - ik - k - 1nikk1 点中选一个子集用 m−1m - 1m1 种颜色染色,相当于将这 n−ik−1n - ik - 1nik1 个点用 mmm 种颜色染色(mn−ik−k−1m^{n - ik - k - 1}mnikk1);
    2. 关键点在 kkk 个点之中:
      [1] 在 kkk 个点中选一个关键点(kkk);
      [2] 将其余 k−1k - 1k1 个点用 (m−1)(m - 1)(m1) 种颜色染色((m−1)k−1(m - 1)^{k - 1}(m1)k1);
      [3] 在剩下的 n−ik−kn - ik - knikk 个点中选一个子集用 m−1m - 1m1 种颜色染色,计算与上面同理(mn−ik−km^{n - ik - k}mnikk
  • 最终整理得到答案的式子为:n×mn−m∑i=0n−1∑k=0n(−1)kn−ikCn−ikk[(n−ik−k)(m−1)kmn−ik−k−1+k(m−1)k−1mn−ik−k]n \times m^{n} - m\sum \limits_{i = 0}^{n - 1} \sum \limits_{k = 0}^{n} \frac{(-1)^k}{n - ik} C_{n - ik}^{k}[(n - ik - k)(m - 1)^k m^{n - ik - k -1} + k(m - 1)^{k - 1} m^{n - ik - k}]n×mnmi=0n1k=0nnik(1)kCnikk[(nikk)(m1)kmnikk1+k(m1)k1mnikk]
  • 预处理 m,m−1m, m - 1m,m1 的次幂以及 111~nnn 阶乘、111~nnn 逆元、 111~nnn 阶乘逆元,即可在 O(nlog⁡n)O(n \log n)O(nlogn) 的复杂度内计算上述式子。

Code

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cctype>
#include <cstdio>
#include <cmath>
#include <ctime>

template <class T>
inline void read(T &res)
{
	char ch; bool flag = false; res = 0;
	while (ch = getchar(), !isdigit(ch) && ch != '-');
	ch == '-' ? flag = true : res = ch ^ 48;
	while (ch = getchar(), isdigit(ch))
		res = res * 10 + ch - 48;
	flag ? res = -res : 0; 
}

const int N = 3e5 + 5;
int fra[N], inv_fra[N], inv[N], ex_m[N], ex_m1[N];
int n, m, mod;

inline void add(int &x, int y)
{
	x += y;
	x >= mod ? x -= mod : 0;
}

inline int quick_pow(int x, int k)
{
	int res = 1;
	while (k)
	{
		if (k & 1) res = 1ll * res * x % mod;
		x = 1ll * x * x % mod; k >>= 1;
	}
	return res;
}

inline int C(int n, int m)
{
	return 1ll * fra[n] * inv_fra[n - m] % mod * inv_fra[m] % mod;
}

int main()
{
	freopen("sequence.in", "r", stdin);
	freopen("sequence.out", "w", stdout);
	
	read(n); read(m); read(mod);
	ex_m[0] = ex_m1[0] = fra[0] = 1;
	for (int i = 1; i <= n; ++i)
	{
		fra[i] = 1ll * fra[i - 1] * i % mod;
		ex_m[i] = 1ll * ex_m[i - 1] * m % mod;
		ex_m1[i] = 1ll * ex_m1[i - 1] * (m - 1) % mod;
	}
	
	inv_fra[n] = quick_pow(fra[n], mod - 2);
	for (int i = n; i >= 1; --i)
		inv_fra[i - 1] = 1ll * inv_fra[i] * i % mod;
	inv[0] = 1;
	for (int i = 1; i <= n; ++i)
		inv[i] = 1ll * fra[i - 1] * inv_fra[i] % mod;
	
	int ans = 0;
	for (int i = 0; i < n; ++i)
		for (int k = 0, km = n / (i + 1); k <= km; ++k)
		{
			int t = n - i * k - k,                                                       
				sit1 = 1ll * k * (k > 0 ? ex_m1[k - 1] : 0) % mod * ex_m[t] % mod,
				sit2 = 1ll * t * ex_m1[k] % mod * (t > 0 ? ex_m[t - 1] : 0) % mod;
			int tmp = 1ll * inv[t + k] * C(t + k, k) % mod * (sit1 + sit2) % mod;
			add(ans, (k & 1) ? mod - tmp : tmp);
		}
	ans = 1ll * (mod - m) * ans % mod;
	add(ans, 1ll * n * ex_m[n] % mod);
	printf("%d\n", ans);
	
	fclose(stdin); fclose(stdout);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值