题目大意
有n个学生,每个学生有一个能力值aia_iai,把学生分成任意组,所有组的最大值减最小值的和不超过给定值k的方案数有多少。
题解
因为每一组与之有关的只是最大值最小值,中间值没有影响,所以可以忽略中间值,可以利用差分的思想,把这一组看成一个线段。这一组的的贡献就是:
(ai+k−ai+k−1)+(ai+k−1−ai+k−2)+⋯+(ai+1−ai)=(ai+k−ai)(a_{i+k}-a_{i+k-1})+(a_{i+k-1}-a_{i+k-2})+\cdots+(a_{i+1}-a_{i})=(a_{i+k}-a_{i})(ai+k−ai+k−1)+(ai+k−1−ai+k−2)+⋯+(ai+1−ai)=(ai+k−ai)
aia_iai为递增的数列.
所以可以先将学生的能力值从小到大进行排序,f[i][j][k]f[i][j][k]f[i][j][k]代表从前i个学生,有j个组没有确定右端点(右端点在剩下的学生里),已经贡献为k的组数。
那么新添加一个学生ai+1a_{i+1}ai+1会增加一个线段ai+1−aia_{i+1}-a_{i}ai+1−ai,由于有jjj个学生没有确定右端点,所以增加一个学生产生的贡献为(ai+1−ai)×j(a_{i+1}-a_{i} )\times j(ai+1−ai)×j。
会有以下四种情况出现:
1.该点自己成为一个集合,但是对未确定右端点的集合会增加贡献(ai+1−ai)×j(a_{i+1}-a_{i} )\times j(ai+1−ai)×j
f[i+1][j][k]+=f[i][j][k−(ai+1−ai)×j]f[i+1][j][k]+=f[i][j][k-(a_{i+1}-a_{i} )\times j]f[i+1][j][k]+=f[i][j][k−(ai+1−ai)×j]
2.如果该点成为jjj个集合中某一个集合的右端点,就少一个未确定的集合,但是jjj个集合都有可能,有jjj种情况,结果要乘jjj
f[i+1][j−1][k]+=f[i][j][k−(ai+1−ai)×j]×jf[i+1][j-1][k]+=f[i][j][k-(a_{i+1}-a_{i} )\times j]\times jf[i+1][j−1][k]+=f[i][j][k−(ai+1−ai)×j]×j
3.如果增加的点成为未确定右端点的集合的中间元素,那么对jjj个集合都有可能。
f[i+1][j][k]+=f[i][j][k−(ai+1−ai)×j]×jf[i+1][j][k]+=f[i][j][k-(a_{i+1}-a_{i} )\times j]\times jf[i+1][j][k]+=f[i][j][k−(ai+1−ai)×j]×j
4.如果增加的点作为一个新的未确定集合的左端点,则方程为
f[i+1][j+1][k]+=f[i][j][k−(ai+1−ai)×j]f[i+1][j+1][k]+=f[i][j][k-(a_{i+1}-a_{i} )\times j]f[i+1][j+1][k]+=f[i][j][k−(ai+1−ai)×j]
c=j*(a[i]-a[i-1]);
f[i+1][j][k+c]+=f[i][j][k];//作为独立一个点
f[i+1][j-1][k+c]+=f[i][j][k]*j;//作为右端点
f[i+1][j][k+c]+=f[i][j][k]*j;//作为中间点
f[i+1][j+1][k+c]+=f[i][j][k];//作为左端点
最后结果就是∑i=0kf[n][0][i]\sum\limits_{i=0}^{k}f[n][0][i]i=0∑kf[n][0][i]
还可以进一步优化空间:f[i][j][k]f[i][j][k]f[i][j][k]只影响f[i+1][j][k]f[i+1][j][k]f[i+1][j][k]
f[(i+1)%2][j][k+c]+=f[i%2][j][k];
f[(i+1)%2][j-1][k+c]+=f[i%2][j][k]*j;
f[(i+1)%2][j][k+c]+=f[i%2][j][k]*j;
f[(i+1)%2][j+1][k+c]+=f[i%2][j][k];
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define N 201
ll mod=1e9+7;
int n,m;
int A[N];
ll dp[2][N][1001];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>A[i];
}
sort(A+1,A+n+1);
dp[0][0][0]=1;
for(int i=0;i<n;i++){
int c=A[i+1]-A[i];
for(int j=0;j<=n;j++){
int t=j*c;
for(int k=0;k<=m;k++){
if(k>m-t){
dp[i%2][j][k]=0;
continue;
}
if(j!=0)
dp[(i+1)%2][j-1][k+t]=(dp[(i+1)%2][j-1][k+t]+(1ll*dp[i%2][j][k]*j)%mod)%mod;
if(j!=n)
dp[(i+1)%2][j+1][k+t]=(dp[(i+1)%2][j+1][k+t]+dp[i%2][j][k])%mod;
dp[(i+1)%2][j][k+t]=(dp[(i+1)%2][j][k+t]+(1ll*dp[i%2][j][k]*j)%mod)%mod;
dp[(i+1)%2][j][k+t]=(dp[(i+1)%2][j][k+t]+dp[i%2][j][k])%mod;
dp[i%2][j][k]=0;
}
}
}
ll res=0;
for(int i=0;i<=m;i++){
res=(res+dp[n%2][0][i])%mod;
}
cout<<res<<endl;
}