【CodeForces】626 F. Group Projects 动态规划
【题意】给定k和n个数字ai,要求分成若干集合使得每个集合内部极差的总和不超过k的方案数。n<=200,m<=1000,1<=ai<=500。
【算法】动态规划
【题解】每个集合的最小值和最大值非常重要,将序列从小到大排序后,每个集合可以视为最小值到最大值的一条线段。
设$f[i][j][k]$表示前i个数,当前有j条线段没有结束,总和为k的方案数。
转移的关键在于集合权值的拆分,转化为算每个数的贡献。数字a[i+1]的贡献就是覆盖的线段条数,即$t=(a[i+1]-a[i])*j$,分类讨论:
是一条线段的起点和终点
$$f[i+1][j][k+t]+=f[i][j][k]$$
即不是起点,又不是终点
$$f[i+1][j][k+t]+=f[i][j][k]*j$$
是起点,不是终点
$$f[i+1][j+1][k+t]+=f[i][j][k]$$
是终点,不是起点
$$f[i+1][j-1][k+t]+=f[i][j][k]*j$$
复杂度O(n^2*m)。
#include<cstdio> #include<algorithm> using namespace std; const int N=210,K=1010,MOD=1e9+7; int n,m,a[N],f[N][N][K]; int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++)scanf("%d",&a[i]); sort(a+1,a+n+1); f[1][1][0]=f[1][0][0]=1; for(int i=1;i<=n;i++) for(int j=0;j<=n;j++){ int t=(a[i+1]-a[i])*j; for(int k=0;k+t<=m;k++){ int x=f[i][j][k]; f[i+1][j][k+t]=(f[i+1][j][k+t]+1ll*x*(j+1))%MOD; f[i+1][j+1][k+t]=(f[i+1][j+1][k+t]+x)%MOD; if(j)f[i+1][j-1][k+t]=(f[i+1][j-1][k+t]+1ll*x*j)%MOD; } } int ans=0; for(int i=0;i<=m;i++)ans=(ans+f[n][0][i])%MOD; printf("%d",ans); return 0; }