一、题目
二、解法
这种绝对值求和可以当成一种模型来积累了,套路是微元贡献法(在 \(\tt CF\) 的一道网络流题也出现过)
我们先把权值离散化,对于离散化后的 \(i<j\) 的 \(|v_j-v_i|=\sum_{k=i}^{j-1}v_{k+1}-v_{k}\),那么 \(v_{k+1}-v_k\) 的贡献是满足 \(i\leq k,k+1\leq 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)\times(2j-d)\):
- 作为一个新的连续段插入到不为边界的空隙中:\(f[i+1][j+1][k'][d]\leftarrow f[i][j][k][d]\times (j+1-d)\)
- 合并两个连续段:\(f[i+1][j-1][k'][d]\leftarrow f[i][j][k][d]\times(j-1)\)
- 添加到某个连续段非边界的端点处:\(f[i+1][j][k'][d]\leftarrow f[i][j][k][d]\times(2j-d)\)
- 作为一个新的连续段,插到边界处:\(f[i+1][j+1][k'][d+1]\leftarrow f[i][j][k][d]\times (2-d)\)
- 添加到某个连续段作为边界:\(f[i+1][j][k'][d+1]\leftarrow f[i][j][k][d]\times(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\n",ans);
}