TopCoder SRM 572 Div2 1000 DistinctRemainders

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

f [ i ] [ j ] C ( ( N j ) / M + i 1 , i 1 ) i !

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

为方便计算,上式进一步简化就是 f [ 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;
}

猜你喜欢

转载自blog.csdn.net/ymzqwq/article/details/82084551