AtCoder Regular Contest 104

AtCoder Regular Contest 104

AtCoder Regular Contest 104

D - Multiset Mean

思路:

首先需要进行转换,对于平均数m, ( ∑ x ∈ S x ) / ∣ S ∣ = m ({\sum_{x\in S}x})/{|S|}=m (xSx)/S=m,可以处理成 ∑ x ∈ S ( x − m ) = 0 \sum_{x\in S}(x-m)=0 xS(xm)=0 ,举个例子。

x ∈ { 1 , 2 , 3 , 4 , 5 , 6 } x\in \{1,2,3,4,5,6\} x{1,2,3,4,5,6},平均数m=3,可以将其处理成 ( x − m ) ∈ { − 2 , − 1 , 0 , 1 , 2 , 3 , 4 } (x-m)\in\{-2,-1,0,1,2,3,4\} (xm){2,1,0,1,2,3,4}。求和为0的方案数。

我们可选两个子集S={1,2…m-1},T={1,2…n-m}。对应上述例子,S={1,2},T={1,2,3,4},当两集合选k个元素所构成的和 s u m sum sum相同,即平均数为m。下面就需要 d p dp dp处理。


状态划分:

d p [ i ] [ j ] dp[i][j] dp[i][j] { 1 , 2... i } \{1,2...i\} {1,2...i}组成的和为 j j j的方案数,其中选的 i i i不超过 k k k个。(其实就是多重背包)

状态转移:

多重背包转移方程: d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + d p [ i − 1 ] [ j − z ∗ i ] , z ∈ { 0 , 1... k } dp[i][j]=dp[i-1][j]+dp[i-1][j-z*i],z\in\{0,1...k\} dp[i][j]=dp[i1][j]+dp[i1][jzi]z{0,1...k}这是最朴素的转移,可惜会超时,时间复杂度为 O ( n 3 k 2 ) O(n^3k^2) O(n3k2)

优化:

这里大概用到完全背包优化+容斥:

0 ≤ j < ( k + 1 ) ∗ i 0\le j< (k+1)*i 0j<(k+1)i时, d p [ i ] [ j ] = d p [ i ] [ j ] + d p [ i ] [ j − i ] dp[i][j]=dp[i][j]+dp[i][j-i] dp[i][j]=dp[i][j]+dp[i][ji]

否则, d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + d p [ i ] [ j − i ] − d p [ i ] [ j − i ∗ ( k + 1 ) ] dp[i][j]=dp[i-1][j]+dp[i][j-i]-dp[i][j-i*(k+1)] dp[i][j]=dp[i1][j]+dp[i][ji]dp[i][ji(k+1)]

这样少去了枚举k,时间复杂度为 O ( n 3 k ) O(n^3k) O(n3k)


组合计数算答案

大概根据组合计数,对于平均数 i i i c n t = d p [ i − 1 ] [ j ] ∗ d p [ n − i ] [ n ] cnt=dp[i-1][j]*dp[n-i][n] cnt=dp[i1][j]dp[ni][n],这是集合S与T的组合数,然后 i i i可以取 { 0 , 1... k } \{0,1...k\} {0,1...k},即有 k + 1 k+1 k+1种取法。 a n s = c n t ∗ ( k + 1 ) − 1 ans=cnt*(k+1)-1 ans=cnt(k+1)1,最后的 − 1 -1 1将空集减去

代码实现

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=101;
int n,k,mod;
ll dp[N][N*N*N];
int main(){
    scanf("%d %d %d",&n,&k,&mod);
    dp[0][0]=1;
    for(int i=1;i<=n;i++){
        for(int j=0;j<=i*i*k;j++){
            dp[i][j]=dp[i-1][j];
            if(j>=i) dp[i][j]=(dp[i][j]+dp[i][j-i])%mod;
        }
        for(int j=i*i*k;j>=i*(k+1);j--){
            dp[i][j]=(dp[i][j]+mod-dp[i][j-i*(k+1)])%mod;
        }
    }

    for(int i=1;i<=n;i++){
        ll ans=0;
        for(int j=0;j<=i*i*k;j++){
                ans=(ans+dp[i-1][j]*dp[n-i][j])%mod;
        }
        ans=(ans*(k+1)%mod+mod-1)%mod;
        printf("%lld\n",ans);
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值