TopCoder SRM 572 Div2 1000 DistinctRemainders

博客围绕KK个数模mm两两互不相同的问题展开,类似0/1背包,采用DP方法,用f[i][j]表示ii个在[0,M−1]的数和为jj的方案数。分析了f[i][j]对答案的贡献,考虑数加mm整数倍后模不变但和增大的情况,还将方案数计算进一步简化。

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

题目说了KK个数模m两两互不相同,有点类似0/1背包,每个剩余类只能选一个,可以用f[i][j]f[i][j]表示ii个在[0,M1]的数和为jj的方案数,DP一波。
对于f[i][j],若jN(mod M)j≡N(mod M)j<=Nj<=N,则对答案的贡献为

f[i][j]C((Nj)/M+i1,i1)i!f[i][j]∗C((N−j)/M+i−1,i−1)∗i!

为什么呢?考虑给每个数加上mm的整数倍之后模m不会发生变化,所以肯定还是符合模mm两两互不相同,然而和却神不知鬼不觉地增大了。所以要使总和为N就相当于把(Nj)/M(N−j)/MMM分给i个数,套用经典的小球模型就是把(Nj)/M(N−j)/M个小球分给ii个盒子,可以为空,这个方案数是C((Nj)/M+i1,i1),题目里是有序的,所以还要乘个阶乘。

为方便计算,上式进一步简化就是f[i][j]A((Nj)/M+i1,i1)if[i][j]∗A((N−j)/M+i−1,i−1)∗i

代码中的变量名和题目不太一样

//tc is healthy, just do it
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2501;
const int M=51;
const int p=1e9+7;
ll f[M][N];

class DistinctRemainders {
public:
    int howMany( long long N, int M );
};

void Add(ll &x,ll y){
    x+=y;
    while(x>=p) x-=p;
}

ll A(ll n,int m){
    if (n<m) return 0;
    ll ans=1;
    for(int i=1;i<=m;i++)
     ans=ans*((n-i+1)%p)%p;
    return ans;
}

int DistinctRemainders::howMany(long long s, int m) {
    f[0][0]=1;
    int sum=0;
    for(int i=0;i<m;i++){
        sum+=i;
        for(int j=i+1;j;j--)  //这里要倒着for,原理类似于0/1背包的倒着for
         for(int k=i;k<=sum;k++)
          Add(f[j][k],f[j-1][k-i]);
    }
    ll y=s%m;
    ll ans=0;
    for(int i=1;i<=m;i++)
     for(ll j=y;j<=s&&j<=sum;j+=m)
      Add(ans,f[i][j]*A((s-j)/m+i-1,i-1)%p*i%p);
    return (int)ans;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值