CF1516E Baby Ehab Plays with Permutations(牛逼计数)

题意

给定 n , K n,K n,K,每次可以交换两个数,问长度为 n n n 的有序排列经过 k k k 次交换后能生成多少排列,答案对 1 e 9 + 7 1e9+7 1e9+7 取模。
n ≤ 1 0 9 , K ≤ 200 n\le 10^9, K\le 200 n109,K200

分析

如果你有看题解,会发现我只是翻译了一遍题解=.=
但是这也没有办法,因为我实在太菜了+。+

普通做法

d p n , k dp_{n,k} dpn,k 表示长度为 n n n 的排列必须经过 k k k 次交换才能生成的排列数(也就是小于 k k k 次生成不了这个排列),那么 a n s k = d p n , k + d p n , k − 2 + d p n , k − 4 . . . + d p n , k % 2 ans_k=dp_{n,k}+dp_{n,k-2}+dp_{n,k-4}...+dp_{n,k\%2} ansk=dpn,k+dpn,k2+dpn,k4...+dpn,k%2
由于 k k k 次操作后,最多 2 k 2k 2k 个位置发生改变。我们可以枚举发生改变的位置数 n ′ n' n。现在问题就变成了长度为 n ′ n' n 的有序排列,交换 k k k 次后的排列数,其中 n ′ ≤ 2 k n'\le 2k n2k
但是这样统计会发生重复,于是我们钦定生成的排列必须是一个错位排列,也就是 k k k 次交换后,对于任意 i ∈ [ 1 , n ′ ] i\in[1,n'] i[1,n] i ≠ p i i\neq p_i i=pi。这样子我们对每个 n ′ n' n,都会产生独特的贡献。
接下来我们考虑怎么求一个大小为 n n n 的排列必须经过 k k k 次交换后才能生成的错位排列数。
f n , k f_{n,k} fn,k 表示大小为 n n n 的排列必须经过 k k k 次交换后能生成的错位排列数, g n , k g_{n,k} gn,k 表示大小为 n n n 的排列必须经过 k k k 次交换后才能生成的排列数,根据容斥原理,我们可以得到: f n , k = ∑ i = 0 n ( − 1 ) i C ( n , i ) g n − i , k f_{n,k}=\sum\limits_{i=0}^{n}(-1)^iC(n,i)g_{n-i,k} fn,k=i=0n(1)iC(n,i)gni,k
接下来我们考虑计算 g n , k g_{n,k} gn,k,我们倒着思考,看看有多少排列必须经过 k k k 次交换后会变成有序排列。我们考虑递推,如果第 n n n 个位置已经是 n n n,那么有 g n − 1 , k g_{n-1,k} gn1,k 个这种排列,否则需要先把 n n n 放到第 n n n 个位置,剩下的再进行排列,有 ( n − 1 ) g n − 1 , k − 1 (n-1)g_{n-1,k-1} (n1)gn1,k1个这种排列。因此, g n , k = g n − 1 , k + ( n − 1 ) g n − 1 , k − 1 g_{n,k}=g_{n-1,k}+(n-1)g_{n-1,k-1} gn,k=gn1,k+(n1)gn1,k1
那么必须经过 k k k 次交换生成的排列数 d p n , k dp_{n,k} dpn,k 就为 ∑ i = 0 m i n ( n , 2 k ) C ( n , i ) f i , k \sum\limits_{i=0}^{min(n,2k)}C(n,i)f_{i,k} i=0min(n,2k)C(n,i)fi,k
这样子好像就做完了呀=.=
代码如下。复杂度是 O ( K 3 ) O(K^3) O(K3) 的。

#include <bits/stdc++.h>
#define all(x) x.begin(), x.end()
#define pii pair<int, int>
#define fi first
#define se second
using namespace std;
typedef long long LL;
const int N = 405, maxn = 400, mod = 1e9 + 7;
int c[N][N], dp[N][N], ans[2], inv[N];
int C(int n, int m){
	int s = 1;
	for(int i = n; i >= n - m + 1; i--) s = (LL)s * i % mod;
	for(int i = 1; i <= m; i++) s = (LL)s * inv[i] % mod;
	return s;
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	int n, k;
	cin >> n >> k;
	inv[1] = 1;
	for(int i = 2; i <= maxn; i++) inv[i] = (LL)(mod - mod / i) * inv[mod % i] % mod;
	for(int i = 0; i <= maxn; i++){
		c[i][0] = dp[i][0] = 1;
		for(int j = 1; j <= i; j++){
			c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;
			dp[i][j] = (dp[i - 1][j] + (LL)(i - 1) * dp[i - 1][j - 1] % mod) % mod;
		}
	}
	ans[0] = 1;
	for(int i = 1; i <= k; i++){
		for(int j = 0; j <= min(k * 2, n); j++){
			int s1 = C(n, j), s2 = 0;
			for(int t = 0, cof; t <= j; t++){
				if(t % 2) cof = mod - 1;
				else cof = 1;
				s2 = (s2 + (LL)cof * c[j][t] % mod * dp[j - t][i] % mod) % mod;
			}
			ans[i % 2] = (ans[i % 2] + (LL)s1 * s2 % mod) % mod;
		}
		cout << ans[i % 2] << ' ';
	}
	return 0;
}

进阶做法

如果 k k k 更大的话,我们应该怎么做这题呢?
我们得重新考虑 d p n , k dp_{n,k} dpn,k 的求法。根据上面的推理,我们也可以得到 d p n , k = d p n − 1 , k + ( n − 1 ) d p n − 1 , k − 1 dp_{n,k}=dp_{n-1,k}+(n-1)dp_{n-1,k-1} dpn,k=dpn1,k+(n1)dpn1,k1。我们从另一个意义上来看 d p n , k dp_{n,k} dpn,k,会发现是从 [ 0 , n − 1 ] [0,n-1] [0,n1] 中选出 k k k 个数乘起来,再求和。形式化的, d p n , k = ∑ s ⊆ { 0 , 1 , 2 , . . . , n − 1 } , ∣ s ∣ = k ∏ x ∈ s x dp_{n,k}=\sum\limits_{s\subseteq\{0,1,2,...,n-1\},|s|=k}\prod\limits_{x\in s}x dpn,k=s{0,1,2,...,n1},s=kxsx
我们考虑倍增求 d p n , k , k ∈ [ 0 , K ] dp_{n,k},k\in[0,K] dpn,k,k[0,K],假设目前得到了 d p n dp_{n} dpn 的生成函数,我们要求 d p 2 n dp_{2n} dp2n 的生成函数。令 d p n , k ′ = ∑ s ⊆ { n , n + 1 , n + 2 , . . . , 2 n − 1 } , ∣ s ∣ = k ∏ x ∈ s x = ∑ s ⊆ { 0 , 1 , 2 , . . . , n − 1 } , ∣ s ∣ = k ∏ x ∈ s ( x + n ) dp'_{n,k}=\sum\limits_{s\subseteq\{n,n+1,n+2,...,2n-1\},|s|=k}\prod\limits_{x\in s}x=\sum\limits_{s\subseteq\{0,1,2,...,n-1\},|s|=k}\prod\limits_{x\in s}(x+n) dpn,k=s{n,n+1,n+2,...,2n1},s=kxsx=s{0,1,2,...,n1},s=kxs(x+n)。我们将 d p n dp_n dpn d p n ′ dp'_n dpn 做一次卷积,就得到了 d p 2 n dp_{2n} dp2n
现在关键就是求 d p n ′ dp'_{n} dpn 了。而 d p n , k ′ = ∑ s ⊆ { 0 , 1 , 2 , . . . , n − 1 } , ∣ s ∣ = k ∏ x ∈ s ( x + n ) = ∑ ( x 1 + n ) ( x 2 + n ) . . . ( x k + n ) dp'_{n,k}=\sum\limits_{s\subseteq\{0,1,2,...,n-1\},|s|=k}\prod\limits_{x\in s}(x+n)=\sum(x_1+n)(x_2+n)...(x_k+n) dpn,k=s{0,1,2,...,n1},s=kxs(x+n)=(x1+n)(x2+n)...(xk+n),假设 k k k 项里面选了 j j j x x x,那么会有 k − j k-j kj n n n,我们枚举 j j j,那么 j j j x x x 的贡献就是 n k − j × d p n , j n^{k-j}\times dp_{n,j} nkj×dpn,j,而总共有 n n n 个数,剩下的 x x x 选法就是 C ( n − j , k − j ) C(n-j,k-j) C(nj,kj)。因此, d p n , k ′ = ∑ j = 0 k C ( n − j , k − j ) n k − j d p n , k dp'_{n,k}=\sum\limits_{j=0}^{k}C(n-j,k-j)n^{k-j}dp_{n,k} dpn,k=j=0kC(nj,kj)nkjdpn,k
来到这里就做完了!
这样子的复杂度可以是 O ( K 3 l o g n ) O(K^3logn) O(K3logn),也可以做到 O ( K 2 l o g n ) O(K^2logn) O(K2logn)
其实容易注意到 d p ′ dp' dp 的求法也是卷积的形式,如果模数是 n t t ntt ntt 模数或者使用任意模数 n t t ntt ntt ,我们这题可以在 O ( K l o g K l o g n ) O(KlogKlogn) O(KlogKlogn) 复杂度内解决。
代码如下。

#include <bits/stdc++.h>
#define all(x) x.begin(), x.end()
#define pii pair<int, int>
#define fi first
#define se second
using namespace std;
typedef long long LL;

void debug_out(){
    cerr << endl;
}
template<typename Head, typename... Tail>
void debug_out(Head H, Tail... T){
    cerr << " " << to_string(H);
    debug_out(T...);
}
#ifdef local
#define debug(...) cerr<<"["<<#__VA_ARGS__<<"]:",debug_out(__VA_ARGS__)
#else
#define debug(...) 55
#endif

typedef vector<int> poly;
const int N = 205, mod = 1e9 + 7;
int n, k, c[N][N], inv[N], ans[2];
int C(int n, int m){
	int s = 1;
	for(int i = n; i >= n - m + 1; i--) s = (LL)s * i % mod;
	for(int i = 1; i <= m; i++) s = (LL)s * inv[i] % mod;
	return s;
}
poly solve(int n){
	if(n == 1) return {1};
	int m = n / 2;
	poly po(k + 1);
	po[0] = 1;
	for(int i = 1; i <= k; i++) po[i] = (LL)po[i - 1] * m % mod;
	poly f = solve(m), g, h;
	f.resize(k + 1);
	g.resize(k + 1);
	for(int i = 0; i <= min(m, k); i++){
		for(int j = 0; j <= i; j++){
			g[i] = (g[i] + (LL)C(m - j, i - j) * po[i - j] % mod * f[j] % mod) % mod;
		}
	}
	if(n & 1){
		for(int i = k; i >= 1; i--) g[i] = (g[i] + (LL)(n - 1) * g[i - 1] % mod) % mod;
	}
	h.resize(k + 1);
	for(int i = 0; i <= min(n, k); i++){
		for(int j = 0; j <= i; j++){
			h[i] = (h[i] + (LL)f[j] * g[i - j] % mod) % mod;
		}
	}
	return h;
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	cin >> n >> k;
	inv[1] = 1;
	for(int i = 2; i <= k; i++) inv[i] = (LL)(mod - mod / i) * inv[mod % i] % mod;
	for(int i = 0; i <= k; i++){
		c[i][0] = 1;
		for(int j = 1; j <= i; j++) c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;
	}
	poly f = solve(n);
	ans[0] = 1;
	for(int i = 1; i <= k; i++){
		ans[i % 2] += f[i];
		debug(i, f[i]);
		if(ans[i % 2] >= mod) ans[i % 2] -= mod;
		cout << ans[i % 2] << ' ';
	}
	return 0;
}

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值