简述题意
给定 n , m n,m n,m,对于一个长度为 n n n 的排列 p p p,有函数: F ( p ) = ∑ i = 1 n [ ∣ p i − i ∣ = 1 ] F(p)=\sum_{i=1}^{n}[\left|p_i-i\right|=1 ] F(p)=i=1∑n[∣pi−i∣=1]求有多少个排列满足 F ( p ) = m F(p) = m F(p)=m,答案对 1 0 9 + 7 10^9+7 109+7 取模。
- 1 ≤ m ≤ n ≤ 1 0 3 1\le m \le n \le 10^3 1≤m≤n≤103
思路
看到这类 “恰好等于” 的问题,很自然想到二项式反演。不妨令 f ( i ) f(i) f(i) 为 F ( p ) ≥ i F(p) \ge i F(p)≥i 的方案数,令 g ( i ) g(i) g(i) 为 F ( p ) = i F(p) = i F(p)=i 的方案数,根据二项式反演得:
g ( m ) = ∑ i = m n f ( i ) × C m i × − 1 i − m g(m)=\sum_{i=m}^{n}f(i) \times C_m^i \times -1^{i-m} g(m)=i=m∑nf(i)×Cmi×−1i−m
考虑如何求出 f ( i ) f(i) f(i)。注意到此题, F ( p ) F(p) F(p) 实质是在统计有多少 i i i 满足: p i = i + 1 p_i=i+1 pi=i+1 或 p i = i − 1 p_i=i-1 pi=i−1。考虑去除排列的限制,即可以使用任意数任意次,那么显然有 f ( i ) = C n i × ( n − i ) ! × 2 i f(i)=C^i_n \times (n-i)! \times 2^i f(i)=Cni×(n−i)!×2i。如果我们加上排列的限制,观察上述式子中的哪些情况变得不合法:
- p 1 = 0 p_1 = 0 p1=0
- p n = n + 1 p_n=n+1 pn=n+1
- p i − 1 = i p_{i-1}=i pi−1=i 且 p i + 1 = i p_{i+1}=i pi+1=i
难点便在于该如何规避掉上述不合法的情况。假设我们钦定满足条件的 i i i 组成的集合 S S S,那么我们只关心集合中的 i i i 位置的取值,剩下未在集合中的位置(即有无贡献皆可)随意乱填即可。
注意到:如果我们处理到第 i i i 个位置,且 i i i 在我们钦定的集合 S S S 里,那么我们只关心 i − 1 i-1 i−1 和 i + 1 i+1 i+1 这两个数在之前是否被选取,因为我们只能在 i i i 位置上填这两个数。即,当前被使用掉的数,一定是某个 i i i 加上 1 1 1 或减去 1 1 1 的结果。如果当前位置填 i − 1 i-1 i−1,那么 i + 1 i+1 i+1 在之前一定未出现过,这启发我们 DP \text{DP} DP。
不妨令
d
p
i
,
j
,
0
/
1
,
0
/
1
dp_{i,j,0/1,0/1}
dpi,j,0/1,0/1 表示处理到第
i
i
i 个位置,钦定
j
j
j 个位置满足条件,且
i
i
i 是/否 被选取,
i
+
1
i+1
i+1 是/否 被选取的方案数。
注意:我们肯定不会定义
i
−
1
i-1
i−1 是/否 出现,
i
+
1
i+1
i+1 是/否 出现这样的状态,否则会产生状态上的冲突,而且会漏掉很多情况。
考虑分类转移:
- 当前位置填
i
−
1
i-1
i−1,那么
i
+
1
i+1
i+1 一定未被选取,且
i
−
1
i-1
i−1 在之前一定未被选取。
d p i , j , 0 , 0 = d p i − 1 , j − 1 , 0 , 0 dp_{i,j,0,0}=dp_{i-1,j-1,0,0} dpi,j,0,0=dpi−1,j−1,0,0, d p i , j , 1 , 0 = d p i − 1 , j − 1 , 0 , 1 dp_{i,j,1,0}=dp_{i-1,j-1,0,1} dpi,j,1,0=dpi−1,j−1,0,1 - 当前位置填
i
+
1
i+1
i+1,那么
i
−
1
i-1
i−1 有可能被选取。
d p i , j , 0 , 1 = d p i − 1 , j − 1 , 0 , 0 + d p i − 1 , j − 1 , 1 , 0 dp_{i,j,0,1}=dp_{i-1,j-1,0,0}+dp_{i-1,j-1,1,0} dpi,j,0,1=dpi−1,j−1,0,0+dpi−1,j−1,1,0, d p i , j , 1 , 1 = d p i − 1 , j − 1 , 0 , 1 + d p i − 1 , j − 1 , 1 , 1 dp_{i,j,1,1}=dp_{i-1,j-1,0,1}+dp_{i-1,j-1,1,1} dpi,j,1,1=dpi−1,j−1,0,1+dpi−1,j−1,1,1 - 当前位置不作贡献。
d p i , j , 0 , 0 = d p i − 1 , j , 0 , 0 + d p i − 1 , j , 1 , 0 dp_{i,j,0,0}=dp_{i-1,j,0,0}+dp_{i-1,j,1,0} dpi,j,0,0=dpi−1,j,0,0+dpi−1,j,1,0, d p i , j , 1 , 0 = d p i − 1 , j , 0 , 1 + d p i − 1 , j , 1 , 1 dp_{i,j,1,0}=dp_{i-1,j,0,1}+dp_{i-1,j,1,1} dpi,j,1,0=dpi−1,j,0,1+dpi−1,j,1,1
注意边界条件: d p 1 , 0 , 0 , 0 = d p 1 , 1 , 0 , 1 = 1 dp_{1,0,0,0}=dp_{1,1,0,1}=1 dp1,0,0,0=dp1,1,0,1=1。
暴力
O
(
n
2
)
O(n^2)
O(n2) 转移得到
d
p
dp
dp 值,然后有:
f
(
i
)
=
(
d
p
n
,
i
,
0
,
0
+
d
p
n
,
i
,
1
,
0
)
×
(
n
−
i
)
!
f(i)=(dp_{n,i,0,0}+dp_{n,i,1,0}) \times (n-i)!
f(i)=(dpn,i,0,0+dpn,i,1,0)×(n−i)!
得出 f ( i ) f(i) f(i) 的值以后,套用二项式反演即可。
代码
#include<bits/stdc++.h>
#define int long long
const int MAXN = 1e3 + 5 , MOD = 1e9 + 7;
using namespace std;
int n , m , dp[MAXN][MAXN][2][2] , ans , jc[MAXN];
int qpow(int base , int k) {
int res = 1;
while(k) {
if (k & 1) res = res * base % MOD;
base = base * base % MOD;
k >>= 1;
}
return res;
}
int inv(int x) {return qpow(x , MOD - 2);};
int C(int x , int y) {return jc[x] * inv(jc[y]) % MOD * inv(jc[x - y]) % MOD;}
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr) , cout.tie(nullptr);
cin >> n >> m;
jc[0] = 1;
for (int i = 1 ; i <= n ; i ++) jc[i] = jc[i - 1] * i % MOD;
dp[1][0][0][0] = dp[1][1][0][1] = 1;
for (int i = 2 ; i <= n ; i ++) {
for (int j = 0 ; j <= i ; j ++) {
// 选 i - 1
dp[i][j][0][0] = dp[i - 1][j - 1][0][0] , dp[i][j][1][0] = dp[i - 1][j - 1][0][1];
// 选 i + 1
dp[i][j][0][1] = (dp[i - 1][j - 1][0][0] + dp[i - 1][j - 1][1][0]) % MOD , dp[i][j][1][1] = (dp[i - 1][j - 1][0][1] + dp[i - 1][j - 1][1][1]) % MOD;
// 不产生贡献
dp[i][j][1][0] = (dp[i][j][1][0] + dp[i - 1][j][0][1] + dp[i - 1][j][1][1]) % MOD;
dp[i][j][0][0] = (dp[i][j][0][0] + dp[i - 1][j][0][0] + dp[i - 1][j][1][0]) % MOD;
}
}
for (int i = m ; i <= n ; i ++) ans = (ans + (dp[n][i][1][0] + dp[n][i][0][0]) % MOD * jc[n - i] % MOD * C(i , m) % MOD * ((i - m & 1) ? -1 : 1) % MOD + MOD) % MOD; // 二项式反演
cout << ans;
return 0;
}