题解:CF1954D(Colored Balls)
CF1954D,是 CodeForces 难得一见的“非多测”题目,我们来看一下。
题意简述:有 n n n 种不同的球,第 i i i 种球有 a i a_i ai 个( 1 ≤ i ≤ n 1\leq i\leq n 1≤i≤n)。对于每一种球,我们都可以选择“用”或者“不用”,因此我们最终会有 2 n 2^n 2n 种不同的选法。对于每种选法,都有一个对应的权值,定义如下:将这些球分成 k k k 份,每一份的数量不大于 2 2 2 且不小于 1 1 1,使得每一份中的球种类不同(如果是两个球,要求它们种类不同;如果是一个球,就无所谓了),权值即为能达成的最小的 k k k。
观察数据范围, 1 ≤ n ≤ 5000 1\leq n\leq5000 1≤n≤5000 且 ∑ a ≤ 5000 \sum a\leq5000 ∑a≤5000,显然这题是一道 O ( n ∑ a ) O(n\sum a) O(n∑a) 的题。
既然它要我们求权值,那我们就思考:怎样快速算出一种选法的权值呢?
对题面中的要求用“白话文”解释一下,就是尽量让更多的种类不同的球两两搭配,剩下的都是一种相同的球,只能孤独终老。因此,最后剩下的相同的那些一定是数量最多的那一种。因此我们分情况讨论:
设这种选法总共有 m m m 种球,其中第 i i i 种有 b i b_i bi 个( 1 ≤ i ≤ m 1\leq i\leq m 1≤i≤m),且 max i = 1 m b i = b 1 \max_{i=1}^m b_i=b_1 maxi=1mbi=b1。
-
当 ∑ i = 2 m b i ≤ b 1 \sum_{i=2}^mb_i\leq b_1 ∑i=2mbi≤b1 时,我们可以将其他种类的 ∑ i = 2 m b i \sum_{i=2}^mb_i ∑i=2mbi 个球与第 1 1 1 种(共有 b 1 b_1 b1 个)中的 ∑ i = 2 m b i \sum_{i=2}^mb_i ∑i=2mbi 进行搭配,剩下的第 1 1 1 种球单独分配。权值为 b 1 b_1 b1。
-
当 ∑ i = 2 m b i > b 1 \sum_{i=2}^mb_i>b_1 ∑i=2mbi>b1 时,刨去奇偶性因素,最终一定不会有单独分配的,因此最终权值为 ⌈ ( ∑ i = 1 m b i ) ÷ 2 ⌉ \lceil(\sum_{i=1}^m b_i)\div 2\rceil ⌈(∑i=1mbi)÷2⌉。
于是,求最大值( b 1 b_1 b1)和其余一堆数的总和( ∑ i = 2 m b i \sum_{i=2}^mb_i ∑i=2mbi)就是这里面唯一耗时严重的部分,我们可以考虑将数组 a a a 从小到大排序,然后 for
一遍每一种,用变量 s u m sum sum 来记录一下目前为止的总和。顺着这个思路,我们再去考虑如何去求答案——当然是基于以上两种情况。于是,我们定义 f i f_i fi 表示目前为止(不算当前的这一种球)能使得取的球的总数为 i i i 的总方案数,那么,对于第一种情况( 1 ≤ j ≤ a i 1\leq j\leq a_i 1≤j≤ai),对答案的贡献是 f[j]*1LL*a[i]
;同理,对于第二种情况,对答案的贡献是 f[j]*1LL*((j+a[i]+1)/2)
(常用技巧: a ÷ 2 a\div2 a÷2 向上取整可以处理成 (a+1)/2
)。对于数组 f f f 的维护,我们就用类似于背包的策略,从大状态到小状态枚举,每次用 j − a i j-a_i j−ai 的状态去更新 j j j 的状态。
给个代码:
#include<bits/stdc++.h>
#define N 5500
#define S 5500
using namespace std;
const int _p=998244353;
int n,a[N];
int f[S],ans,sum;
int main(){scanf("%d",&n);for(int i=1;i<=n;i++)scanf("%d",&a[i]);sort(a+1,a+1+n);f[0]=1;for(int i=1;i<=n;i++){for(int j=0;j<=sum;j++){if(j<=a[i])ans+=(f[j]*1LL*a[i])%_p;else ans+=(f[j]*1LL*((j+a[i]+1)/2))%_p;ans%=_p;}sum+=a[i];for(int j=sum;j-a[i]>=0;j--)f[j]=(f[j]+f[j-a[i]])%_p;}printf("%d\n",ans);return 0;
}
注意:
在维护数组 f f f 的时候一定要注意不要减着减着就出负数状态了;
中间计算要开 long long
并取模。