P7967 题解

First Post:

Last Update:

不算很难,但并不是那么好想。

直接 DP 每个磁铁属于哪个位置是非常麻烦的,难以满足“排列”的限制。所以不妨直接确定最终磁铁的排列 ,那么 之间的距离就至少是 。设这个值为 的实际距离为 。那么我们要求 。运用一些双射技巧,将第 个磁铁的坐标减去 ,那么减之前的坐标序列与减之后的坐标序列构成双射,同时新的坐标只需要满足单调递增即可。因为最后一个点的新坐标从 变成了 ,方案数就是 。也可以通过对 插板得到同样的结论。

那么,一个排列对答案的贡献就只和 有关。而 只与排列的相邻两项有关,所以考虑经典的连续段 DP,按 从小到大,将元素填入排列中。设 表示当前填了 个元素,当前填的元素在最终排列中形成 个连续段,当前 的方案数。

转移如下:

  1. 合并两段:
  2. 接在某一段的开头或末尾:
  3. 新开一段:

最后的答案就是

于是本题就做完了,时间复杂度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <bits/stdc++.h>
using namespace std;
const int N = 55,M = 1e4 + 5,P = 1e9 + 7;
inline void Plus(int &x,const int &y) { x += y;if(x >= P) x -= P;}
int n,L;
int r[N];
int dp[N][N][M];
int fac[M + M],ifac[M + M];
void init(int n) {
fac[0] = 1;
for(int i = 1;i <= n;i++) fac[i] = 1ll * fac[i - 1] * i % P;
ifac[1] = 1;
for(int i = 2;i <= n;i++) ifac[i] = 1ll * ifac[P % i] * (P - P / i) % P;
ifac[0] = 1;
for(int i = 1;i <= n;i++) ifac[i] = 1ll * ifac[i - 1] * ifac[i] % P;
}
inline int C(int n,int m) { if(n < 0 || m < 0 || n < m) return 0;return 1ll * fac[n] * ifac[m] % P * ifac[n - m] % P;}
int main() {
cin >> n >> L;
for(int i = 1;i <= n;i++) cin >> r[i];
sort(r + 1,r + n + 1);
dp[0][0][0] = 1;
for(int i = 1;i <= n;i++)
for(int j = 0;j < i;j++)
for(int k = 0;k <= L;k++) if(dp[i - 1][j][k]) {
if(j && k + 2 * r[i] <= L) Plus(dp[i][j - 1][k + 2 * r[i]],1ll * dp[i - 1][j][k] * (j - 1) % P); // 合并两段
if(k + r[i] <= L) Plus(dp[i][j][k + r[i]],1ll * dp[i - 1][j][k] * (j + j) % P); // 拓展一段
Plus(dp[i][j + 1][k],1ll * dp[i - 1][j][k] * (j + 1) % P); // 新开一段
}
init(L + n);
int ans = 0;
for(int k = 0;k <= L;k++)
Plus(ans,1ll * dp[n][1][k] * C(L - k + n - 1,n) % P);
cout << ans << endl;
return 0;
}