题目大意
有n个数
两种分组方案不同当且仅当存在两个数在其中一种方案中在同组,另一种方案中在不同组。
1≤n≤200,0≤K≤1000,1≤si≤500
题目分析
30分算法
先来看看这题的部分分:∑si≤1000
将所有数从小到大排序,令fi,j,k表示考虑到第i个数,目前有
- 当前位置单独成组
- 当前位置填入某一组,但不作为最大值
- 当前位置新建一个组,作为最小值(极差和减去
si )- 当前位置填入某一组,并且作为最大值(极差和加上si)
最后的答案是∑Kk=0fn,0,k。当然从大到小排序k恒为正数会更方便,这里只是为了和下文顺序保持一致才从小到大排。
这样计算时间复杂度是O(n2∑si) 的。100分算法
考虑优化30分算法。
使用差分的思想,令di=si−si−1,可以发现si−sj变为∑ik=j+1wk。
这样的话,我们每个位置对答案的贡献显然可以写成wi×j,即对每一组都有wi的贡献。转移的话依然是分四种情况讨论,很简单的就不细讲了。
注意到如果我们从小到大做,那么极差和是不减的,于是第三位只用开到K即可。
时间复杂度减少为O(n2k) 。注意使用滚动数组压缩空间。
代码实现
#include <algorithm> #include <iostream> #include <cstring> #include <cstdio> using namespace std; const int P=1000000007; const int N=205; const int K=1005; int f[2][N][K]; int n,k0,ans; int s[N]; void solve() { sort(s+1,s+1+n); f[1][0][0]=f[1][1][0]=1; for (int i=1,x,y,tmp;i<n;i++) { x=i&1,y=x^1; memset(f[y],0,sizeof f[y]); for (int j=0;j<=i;j++) for (int k=0;k<=k0;k++) if (f[x][j][k]&&(tmp=k+(s[i+1]-s[i])*j)<=k0) { (f[y][j+1][tmp]+=f[x][j][k])%=P; if (j) (f[y][j-1][tmp]+=1ll*f[x][j][k]*j%P)%=P,(f[y][j][tmp]+=1ll*f[x][j][k]*j%P)%=P; (f[y][j][tmp]+=f[x][j][k])%=P; } } ans=0; for (int k=0;k<=k0;k++) (ans+=f[n&1][0][k])%=P; } int main() { freopen("group.in","r",stdin),freopen("group.out","w",stdout); scanf("%d%d",&n,&k0); for (int i=1;i<=n;i++) scanf("%d",&s[i]); solve(); printf("%d\n",ans); fclose(stdin),fclose(stdout); return 0; }