AtCoder Regular Contest 104
D - Multiset Mean
思路:
首先需要进行转换,对于平均数m, ( ∑ x ∈ S x ) / ∣ S ∣ = m ({\sum_{x\in S}x})/{|S|}=m (∑x∈Sx)/∣S∣=m,可以处理成 ∑ x ∈ S ( x − m ) = 0 \sum_{x\in S}(x-m)=0 ∑x∈S(x−m)=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\} (x−m)∈{−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[i−1][j]+dp[i−1][j−z∗i],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 0≤j<(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][j−i]
否则, 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[i−1][j]+dp[i][j−i]−dp[i][j−i∗(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[i−1][j]∗dp[n−i][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);
}
}