Educational Codeforces Round 164

A-C简单数学题先跳过了,E题水平有限,暂时写不出来

下面是D的题意

Colored Ball

(https://codeforces.com/contest/1954/problem/D)

看题目,对于初学者来说,可能不知道使用DP

怎么联想到DP的可能还是经验问题

下面是个人对题目的拙见

题目要求所有幂集组合里需要至少需要多少个二元对才能放下所有的球

这不就是求所有幂集组合下有多少种满足题意的取法吗?

其中相同颜色的球最多放一个

给定输出n,代表有n种颜色的球

下面一行有n个数字,代表每种颜色有n个球

我们看样例

4
1 3 3 7

试想,当我们需要第四种颜色与第一个颜色组合时需要多少组呢?

答案很明显是7个

即是

4 - 1
4
4
4
4
4
4
//数字代表第几种颜色的球

(我们注意到题目标签有一个sort,这个题需要sort的原因我们再下面讨论)

同理我们来看

第三种和第四种组合

应该是

4-3
4-3
4-3
4
4
4
4

也是需要七个

那么当所有一起呢?

答案也是7个

但如果4颜色只有五个呢?

所有一起组合应该是

4-1
4-2
4-3
4-2
4-3
2-3

总球数为12个。

so

我们可以看出,当有n个颜色的球时,所需组数首先应该是$n/2$ ,但如果颜色最多的球超过$n/2$ 时,那么所需组数就是这个最多颜色球的个数

所以有

$ value = max ( (总球数 + 1)/ 2 , 组合中最大的球数) $

int value = max(a[i], (j + a[i] + 1) / 2);           

value代表什么呢?我们继续往下看

上面其实是热身

本题的精髓就在下面的动态规划部分

DP part!

我们首先想,不可能一个一个把所有的幂集的组合暴力求出来对吧?

那么我们如何把幂集的2次方复杂度降低呢?

想来想去

有一个思路

我们是不是可以求有j个球时,有多少组合的数量呢?

所以当从0->总球数,各有多少种情况,全部相加,刚好就能表示所有的情况。

显而易见,我们的$dp$数组就是维护这个的

现在,dp数组如何写呢?

试想,从第一个球开始,取前 i 种球,有多少种情况,累加到答案中,不就行了吗?

for(int i = 1 ; i <= n ; i++)
{
    for(int j = 0 ; j <= s ; j++)//这里计算取前i种球时,有多少种取法
    {
       /*
       下文继续分解
       */ 
    }
    /*
    其他部分
    */
    
    /*
    dp部分
    */
    
}

由上文的非dp部分,我们已经知道ans要如何相加,但我们的$dp$数组如何维护才是本题的难点,对于初学者确实不是很友好(没做过组合题的)

我们先贴上dp部分代码

for(int j = s ; j <= a[i] ; j++)
	f[j] = f[j] + f[j - a[i]];

看着还行,但我们鉴于有很多初学者,我们再写一个二维的dp便于理解

for(int j = a[i] ; j <= s ; j++)
{
	f[i][j] = f[i - 1][j] + f[i - 1][j-a[i]];
}

为什么要这么写呢?

我们看,当取前$ i $个球时,总球数为$ j $时,会有多少种组合情况呢?

这个有点组合数的意味了

我们假设取前$ i-1 $个球时,有$ f[i-1] $ 种取法,那么更新取包含第$i$ 个球的时候会有多少种取法呢?首先我们知道第$i$个球有$a[i]$个,那么前$i-1$个球有$j - a[i]$时,加上$a[i]$ 个球,是不是正好为$j$呢?答案显而易见了,肯定是的。然而,如果正好取$j$个球时,前$i-1$种球也有取法呢?很简单,加上就好了。

所以我们就有了

f[i][j] = f[i - 1][j] + f[i - 1][j-a[i]];

取前$i-1$种球有$j - a[i]$ 个球时的情况,这是候加上$a[i]$个球,我们就得到了前$i$种球,并且一定包含第$i$种球的取法了,然后加上前$i$种球,总球数为$j$时的取法,我们就得到了包含前第$i$种球,所有幂集的取法了

为什么二维$dp$要从后往前呢?

我们都知道$dp$从后往前可以不取重复,具体原理很简单,可以自己模拟一下,或者看其他博客(b站董晓算法),这里默认大家都知道了。

从前到后,就会在$j$累加超过一倍$a[i]$时出现取了第$a[i]$个球的在取一次第$i$个球的情况

比如{<1,2,3,3>},就是取了两次第3种球了,这与集合幂集取法相悖了

因为二维$dp$在本题因为数据较大,会出现空间超限,所以我们还是要改用一维

到此,取前$i$种球,有$j$​个球时的取法,我们就维护完成了

举个例子,取5个球,球数全为1的时候,$dp$数组变化规律如下:

1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1

第一列是$f[0]$ ,因为取$j$个球时,恰好总球数等于$j$ 也就是只取第$i$个球时,我们有一种情况,当然,还需要加上不包含第$i$​个球的情况,比如前4个球,总球数为1,这里就有分别只取1,2,3,4种球的四种情况

下面再分别贴一个样例1和3的$dp$数组变化情况

1 1
1 2 1
1 2 2 2 1
1 1
1 1 0 1 1
1 1 0 2 2 0 1 1
1 1 0 2 2 0 1 2 1 0 2 2 0 1 1

为什么要排序?

前面我们提到要排序,题目tag也有一个sort,这是为什么呢?

还记得我们value的含义吗?

是的,我们要用集合中球数最多的一种球作为主导,而我们dp数组dp过去后,不排序就会出现虽然dp数组没有问题,但取主导部分出了问题,下面看分解:

$ value = max ( (总球数 + 1)/ 2 , 组合中最大的球数)$中组合最大球数出了问题

(比如有两种颜色,第一个颜色有7个,第二个只有1个这种情况)

但是我们排序之后,这里恰好就是$a[i]$,所以我们这里排序就能保证第i个球,球数最多的球就是$a[i]$,当然也可以不排序,详细可以参考官方题解,但是这样就会让码量变大了。

官方的思路大致就是先全部加上总球数除以2乘以dp数组,然后再判断是否有超过总球数一半的球。

代码

下面是官方不需要排序的代码:

#include <bits/stdc++.h>

using namespace std;

const int MOD = 998244353;

int add(int x, int y)
{
    x += y;
    if (x >= MOD)
        x -= MOD;
    return x;
}

int mul(int x, int y)
{
    return x * 1LL * y % MOD;
}

int main()
{
    int n;
    cin >> n;
    vector<int> a(n);
    for (int i = 0; i < n; ++i)
        cin >> a[i];
    int s = accumulate(a.begin(), a.end(), 0);
    vector<int> dp(s + 1);
    dp[0] = 1;
    for (int i = 0; i < n; ++i)
        for (int j = s - a[i]; j >= 0; --j)
            dp[j + a[i]] = add(dp[j + a[i]], dp[j]);
    int ans = 0;
    for (int j = 0; j <= s; ++j)
        ans = add(ans, mul((j + 1) / 2, dp[j]));
    for (int i = 0; i < n; ++i)
        for (int j = 0; j < a[i]; ++j)
            ans = add(ans, mul(a[i] - (a[i] + j + 1) / 2, dp[j]));
    cout << ans << '\n';
}

下面是我的需要排序的代码:

#include <bits/stdc++.h>
using namespace std;
const int mod = 998244353;
int add(int x, int y)
{
    x = (x + y) % mod;
    return x;
}
int mul(int x, int y)
{
    return x * 1LL * y % mod;
}
const int N = 5000 * 5000 + 7;
int a[N];
int f[N];
int ans = 0;
int s = 0;
//-------------------以上为前置的准备-----------------//

int main()
{
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    f[0] = 1;
	int n;
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    sort(a + 1, a + 1 + n);

    for (int i = 1; i <= n; i++)
    {
        for (int j = 0; j <= s; j++)
        {
            int value = max(a[i], (j + a[i] + 1) / 2);
            ans = add(ans, mul(value, f[j]));
        }
        s += a[i];
        for (int j = s; j >= a[i]; j--)
            f[j] = add(f[j], f[j - a[i]]);       
    }
    cout << ans;
    return 0;
}

不知道为什么上传博客园有的地方乱码了,后面再改一下吧

posted @   Blizzard1900  阅读(28)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示