题目大意
一个长度为 n n n的序列 a a a满足每个元素互不相同。试求有多少个种排列方式使得相邻两数只差的绝对值之和小于等于 L L L。 n < = 100 , L < = 1000 , 1 < = A i < = 1000 n <= 100,L <= 1000,1 <= Ai <= 1000 n<=100,L<=1000,1<=Ai<=1000。
分析
这是一道打着简单思考难度较大的
D
P
DP
DP题。首先按照一般的想法一定是从原序列中随机挑出若干个元素按顺序放入新序列中,如果这样做的话难以避免这个烦人的绝对值,且会发现时间复杂度十分不优秀。正难则反,我们不妨考虑先将原序列排好序,然后按顺序选取并随机放到新序列中,那么新序列会形成若干段。所以我们设状态
f
[
i
]
[
j
]
[
k
]
[
l
]
f[i][j][k][l]
f[i][j][k][l]表示排序后的原序列中选取了前
i
i
i个,在新序列中形成了
j
j
j段,总和为
k
k
k,左右断点一共放了
l
l
l个时的方案数。
那么转移分为五种:
- 插入一个独立的段, f [ i + 1 ] [ j + 1 ] [ t ] [ l ] + = f [ i ] [ j ] [ k ] [ l ] ∗ ( j + 1 − l ) f[i + 1][j + 1][t][l] += f[i][j][k][l] * (j + 1 - l) f[i+1][j+1][t][l]+=f[i][j][k][l]∗(j+1−l)
- 插入在一个段的左右两边, f [ i + 1 ] [ j ] [ t ] [ l ] + = f [ i ] [ j ] [ k ] [ l ] ∗ ( 2 ∗ j − l ) f[i +1][j][t][l] += f[i][j][k][l] * (2 * j - l) f[i+1][j][t][l]+=f[i][j][k][l]∗(2∗j−l)
- 插入在两个段中间将两个段合并, f [ i + 1 ] [ j − 1 ] [ t ] [ l ] + = f [ i ] [ j ] [ k ] [ l ] ∗ ( j − 1 ) f[i +1][j - 1][t][l] += f[i][j][k][l] * (j - 1) f[i+1][j−1][t][l]+=f[i][j][k][l]∗(j−1)
- 插入一个独立且在边界上的段, f [ i + 1 ] [ j + 1 ] [ t ] [ l + 1 ] + = f [ i ] [ j ] [ k ] [ l ] ∗ ( 2 − l ) f[i + 1][j + 1][t][l + 1] += f[i][j][k][l] * (2 - l) f[i+1][j+1][t][l+1]+=f[i][j][k][l]∗(2−l)
- 插入在边界上且与相邻的段合并, f [ i + 1 ] [ j ] [ t ] [ l + 1 ] + = f [ i ] [ j ] [ k ] [ l ] ∗ ( 2 − l ) f[i +1][j][t][l + 1] += f[i][j][k][l] *(2 - l) f[i+1][j][t][l+1]+=f[i][j][k][l]∗(2−l)
其中 t t t表示加入 a [ i + 1 ] a[i + 1] a[i+1]元素后新的总和,那么上述转移是非常显而易见的。但是单纯的这么转移我们会发现 t t t那一维的范围非常不友善,所以还要改进。
- 发现我们可以分步算贡献,我们如果所有未闭合的端点左右都填 x x x的总和。可以发现如果当前 x x x变大了,这个总和会增加 ∆ x × ( 2 j − l ) ∆x × (2j − l) ∆x×(2j−l)。
这个操作比较妙,形象一点地说就是每次将每段的两头用 a [ i ] a[i] a[i]补齐,那么在转移过程中就可以保证第三维可以处在一个比较合理的范围。说着比较抽象可以自己写写画画,或者看看下面简洁的代码或许更好懂。
#include <cstdio>
#include <algorithm>
#include <cstring>
#define mem(a) memset(a,0,sizeof a)
#define ll long long
using namespace std;
const ll mo = 1e9 + 7;
const int N = 110;
const int LEN = 1100;
int n,L,a[N],s = 0;
ll ans,f[2][N][LEN][3];
int read()
{
int x = 0;
char ch = getchar();
while (ch < '0' || ch > '9') ch = getchar();
while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
return x;
}
int main()
{
freopen("count.in","r",stdin);
freopen("count.out","w",stdout);
n = read(),L = read();
if (n == 1) {printf("%d\n",1); return 0;}
for (int i = 1; i <= n; i ++) a[i] = read();
sort(a + 1,a + 1 + n);
f[0][0][0][0] = 1;
for (int i = 0; i < n; i ++,s ^= 1)
{
mem(f[s ^ 1]);
for (int j = 0; j <= i; j ++)
for (int k = 0; k <= L; k ++)
for (int l = 0; l <= 2; l ++)
{
int t = k + (a[i + 1] - a[i]) * (2 * j - l);
if (t > L) continue;
(f[s ^ 1][j + 1][t][l] += f[s][j][k][l] * (j + 1 - l) % mo) %= mo;
if (j)
(f[s ^ 1][j - 1][t][l] += f[s][j][k][l] * (j - 1) % mo) %= mo,
(f[s ^ 1][j][t][l] += f[s][j][k][l] * (2 * j - l) % mo) %= mo;
if (l < 2) (f[s ^ 1][j + 1][t][l + 1] += f[s][j][k][l] * (2 - l) % mo) %= mo;
if (l < 2 && j) (f[s ^ 1][j][t][l + 1] += f[s][j][k][l] *(2 - l) % mo) %= mo;
}
}
for (int i = 0; i <= L; i ++)
ans = (ans + f[s][1][i][2]) % mo;
printf("%lld\n",ans);
return 0;
}