[JZOJ4830]分组

这篇博客介绍了如何解决一个OI题目,要求将n个数分组,使得每组内数的极差之和小于等于K,讨论了30分和100分的算法,包括部分分策略、差分思想的应用,以及如何优化时间复杂度至O(n^2 * K)。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目大意

n个数si,要求将其分成若干组,使得每组内数的极差(最大值减去最小值)之和小于等于K。求方案总数模109+7的结果。
两种分组方案不同当且仅当存在两个数在其中一种方案中在同组,另一种方案中在不同组。

1n200,0K1000,1si500


题目分析

30分算法

先来看看这题的部分分:si1000
将所有数从小到大排序,令fi,j,k表示考虑到第i个数,目前有j个组是还要填数的,极差之和为k的方案数。转移分为以下四种情况讨论:

  1. 当前位置单独成组
  2. 当前位置填入某一组,但不作为最大值
  3. 当前位置新建一个组,作为最小值(极差和减去si
    • 当前位置填入某一组,并且作为最大值(极差和加上si
    • 最后的答案是Kk=0fn,0,k。当然从大到小排序k恒为正数会更方便,这里只是为了和下文顺序保持一致才从小到大排。
      这样计算时间复杂度是O(n2si)的。

      100分算法

      考虑优化30分算法。
      使用差分的思想,令di=sisi1,可以发现sisj变为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;
      }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值