题意
给定
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
n≤109,K≤200
分析
如果你有看题解,会发现我只是翻译了一遍题解=.=
但是这也没有办法,因为我实在太菜了+。+
普通做法
设
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,k−2+dpn,k−4...+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
n′≤2k。
但是这样统计会发生重复,于是我们钦定生成的排列必须是一个错位排列,也就是
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=0∑n(−1)iC(n,i)gn−i,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}
gn−1,k 个这种排列,否则需要先把
n
n
n 放到第
n
n
n 个位置,剩下的再进行排列,有
(
n
−
1
)
g
n
−
1
,
k
−
1
(n-1)g_{n-1,k-1}
(n−1)gn−1,k−1个这种排列。因此,
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=gn−1,k+(n−1)gn−1,k−1。
那么必须经过
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=0∑min(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=dpn−1,k+(n−1)dpn−1,k−1。我们从另一个意义上来看
d
p
n
,
k
dp_{n,k}
dpn,k,会发现是从
[
0
,
n
−
1
]
[0,n-1]
[0,n−1] 中选出
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,...,n−1},∣s∣=k∑x∈s∏x。
我们考虑倍增求
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,...,2n−1},∣s∣=k∑x∈s∏x=s⊆{0,1,2,...,n−1},∣s∣=k∑x∈s∏(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,...,n−1},∣s∣=k∑x∈s∏(x+n)=∑(x1+n)(x2+n)...(xk+n),假设
k
k
k 项里面选了
j
j
j 个
x
x
x,那么会有
k
−
j
k-j
k−j 个
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}
nk−j×dpn,j,而总共有
n
n
n 个数,剩下的
x
x
x 选法就是
C
(
n
−
j
,
k
−
j
)
C(n-j,k-j)
C(n−j,k−j)。因此,
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=0∑kC(n−j,k−j)nk−jdpn,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;
}