Codeforces 626F Group Projects

本文详细解析了一道关于学生分组的问题,通过差分思想和动态规划方法,探讨了如何计算在给定条件下,学生分组方案的数量。文章首先介绍了问题背景,随后深入分析了解题思路,包括学生能力值排序、差分贡献计算以及动态规划状态转移方程,并提供了代码实现。

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

题目大意

有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+kai+k1)+(ai+k1ai+k2)++(ai+1ai)=(ai+kai)
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+1ai,由于有jjj个学生没有确定右端点,所以增加一个学生产生的贡献为(ai+1−ai)×j(a_{i+1}-a_{i} )\times j(ai+1ai)×j
会有以下四种情况出现:
1.该点自己成为一个集合,但是对未确定右端点的集合会增加贡献(ai+1−ai)×j(a_{i+1}-a_{i} )\times j(ai+1ai)×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+1ai)×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][j1][k]+=f[i][j][k(ai+1ai)×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+1ai)×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+1ai)×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=0kf[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;


}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值