不算很难,但并不是那么好想。
直接 DP
每个磁铁属于哪个位置是非常麻烦的,难以满足“排列”的限制。所以不妨直接确定最终磁铁的排列
,那么 和 之间的距离就至少是 。设这个值为
, 与 的实际距离为
。那么我们要求 。运用一些双射技巧,将第 个磁铁的坐标减去 ,那么减之前的坐标序列与减之后的坐标序列构成双射,同时新的坐标只需要满足单调递增即可。因为最后一个点的新坐标从
变成了 ,方案数就是 。也可以通过对
插板得到同样的结论。
那么,一个排列对答案的贡献就只和 有关。而
只与排列的相邻两项有关,所以考虑经典的连续段 DP,按 从小到大,将元素填入排列中。设 表示当前填了 个元素,当前填的元素在最终排列中形成
个连续段,当前 的方案数。
转移如下:
- 合并两段:
- 接在某一段的开头或末尾:
- 新开一段:
最后的答案就是 。
于是本题就做完了,时间复杂度 。
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; }
|