一、题目
二、解法
这种绝对值求和可以当成一种模型来积累了,套路是微元贡献法(在 ( t CF) 的一道网络流题也出现过)
我们先把权值离散化,对于离散化后的 (i<j) 的 (|v_j-v_i|=sum_{k=i}^{j-1}v_{k+1}-v_{k}),那么 (v_{k+1}-v_k) 的贡献是满足 (ileq k,k+1leq j) 的对数。根据这个转化题意,我们按从小到大插入 (a) 构成序列,那么 (v_{k+1}-v_k) 的贡献次数就是前 (k) 个数构成连续段的端点个数,因为端点旁边以后一定会插入比 (k) 大的数,就会产生贡献,有点费用提前计算的感觉了。
这提示我们可以使用连续段 (dp) 来解决这个问题,因为边界(端点是 (1/n))并不会产生贡献所以我们要把它单独记录,设 (f[i][j][k][d]) 表示插入了 (i) 个数,有 (j) 个连续段,贡献和是 (k),有 (d) 个端点的方案数,每次增量法考虑一个新数的插入,新的贡献和为 (k'=k+(v_{i+1}-v_i) imes(2j-d)):
- 作为一个新的连续段插入到不为边界的空隙中:(f[i+1][j+1][k'][d]leftarrow f[i][j][k][d] imes (j+1-d))
- 合并两个连续段:(f[i+1][j-1][k'][d]leftarrow f[i][j][k][d] imes(j-1))
- 添加到某个连续段非边界的端点处:(f[i+1][j][k'][d]leftarrow f[i][j][k][d] imes(2j-d))
- 作为一个新的连续段,插到边界处:(f[i+1][j+1][k'][d+1]leftarrow f[i][j][k][d] imes (2-d))
- 添加到某个连续段作为边界:(f[i+1][j][k'][d+1]leftarrow f[i][j][k][d] imes(2-d))
时间复杂度 (O(n^2k)),最后答案显然是 (sum f[n][1][i][2]),但是要特判 (n=1)
三、总结
对于大小关系之类的限制(如绝对值),可以离散化之后取微元再贡献回去。
连续段 (dp) 是解决序列计数问题的利器,学会主动使用它。
#include <cstdio>
#include <algorithm>
using namespace std;
const int M = 105;
const int N = 1005;
const int MOD = 1e9+7;
#define int long long
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,ans,a[M],f[M][M][N][3];
void add(int &x,int y) {x=(x+y)%MOD;}
signed main()
{
n=read();m=read();
if(n==1) {puts("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++) for(int j=0;j<=i;j++)
for(int k=0;k<=m;k++) for(int d=0;d<=2;d++)
{
int kk=k+(2*j-d)*(a[i+1]-a[i]),t=f[i][j][k][d];
if(kk>m || !t) continue;
//Case1:insert an seg without forming boundary
add(f[i+1][j+1][kk][d],t*(j+1-d));
//Case2:merge two seg
if(j) add(f[i+1][j-1][kk][d],t*(j-1));
//Case3:add to a seg
if(j) add(f[i+1][j][kk][d],t*(2*j-d));
//Case4:insert an seg,forming boundary
if(d<2) add(f[i+1][j+1][kk][d+1],t*(2-d));
//Case5:add to a seg,forming boundary
if(d<2 && j) add(f[i+1][j][kk][d+1],t*(2-d));
}
for(int i=0;i<=m;i++) add(ans,f[n][1][i][2]);
printf("%lld
",ans);
}