首页 > 其他分享 >Educational Codeforces Round 164

Educational Codeforces Round 164

时间:2024-04-29 09:24:59浏览次数:27  
标签:Educational 种球 int Codeforces 取法 164 我们 dp 个球

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;
}

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

标签:Educational,种球,int,Codeforces,取法,164,我们,dp,个球
From: https://www.cnblogs.com/Blizzard1900/p/18164948

相关文章

  • Codeforces Round 941 (Div. 1) 题解(A-C)
    比赛链接:https://codeforces.com/contest/1965官解链接:https://codeforces.com/blog/entry/128914比较手速的一场,C与D之间出现了较大的gifficultygap。所幸C题猜得比较快(虽然证明其实比较难),最终rank190,performance2525,成功压线拿下Grandmaster。cpchenpi,堂堂上红!......
  • Codeforces Round 941 (Div. 2)
    A.CardExchange贪心。如果有某个数出现\(k\)次及以上,则通过操作使其数量变为\(k\),再变为其他出现过的数,则会增加至至少\(k\)个,一直进行如上操作,可以发现数组最终只剩\(k-1\)个数;否则为\(n\)。#include<bits/stdc++.h>usingnamespacestd;#definecctieios::......
  • Codeforces Round 941 (Div. 2) D
    好坐牢的一次div2ABC是速通的,结果cf的pretest太弱了。。然后我这次因为想快点,没有再去好好顺一遍思路,状态又不太好,写了一个好简单的错,结果过了。导致我被hack了,爆掉100分。好烦。主要说说这个D。现在能够从算式的层面上理解了这个做法的正确性,就是把二进制位的数字放进去,然后......
  • Solution of Codeforces 1957B
    DescriptionGivenintegers\(n\)and\(k\),findanon-negativesequence\(\{a_n\}\)satisfyingthefollowingconditions:1.\[\sum_{i=1}^na_i=k\]Thebinaryrepresentationof\(a_1\mida_2\mid\dots\mida_n\)hasthemaximumnumbero......
  • Codeforces Round 936 (Div. 2)
    A考虑有\(x\)个数与中位数相同,且在中位数之后,则答案为\(x+1\)(+1是因为中位数本身)。B明显的,每次操作序列的最大子段和。那么操作完以后,继续操作这个区间即可,相当于每次翻倍。假设原序列最大子段和为区间\([l,r]\),则答案为:\[sum(1,n)-sum(l,r)+sum(l,r)\times2^k\]记......
  • Codeforces Round 931 (Div. 2) D2
    挺简单的题目,就是一个普通博弈论套了一个交互的皮。。。但是恶心的就是,交互的皮是真难顶啊,什么sb玩意,我因为爆了int一直给我现实TLEontest1,这我怎么调?不得不说,交互题和普通的题目真是差别太大了,就评测这一块,返回的消息完全无法给我有效的指导。。然后我就直接坐大牢。要是这......
  • Codeforces Round 939 (Div. 2) E1-E2
    CodeforcesRound939(Div.2)E1.Nenevs.Monsters(EasyVersion)题意:有n个怪物,能量等级为\(a_i\),现在可以使用一种法术,使第1个怪物攻击第2个怪物,第2个怪物攻击第3个怪物……第n个怪物攻击第1个怪物,当能量等级为x的怪物攻击能量等级为y的怪物时,怪物y->max(y-x,0),在经过\(1......
  • Codeforces Round 892 (Div. 2) E
    E的话一眼dp,然后观察一下方程,\(f[i][j]表示前i个位置已经选了长度为j的区间,且第i个位置已经被选上时,能够获得的最大值\)\[f[i][j]=\displaystyle\max_{1\leqk\leqmin(i,j)}(f[i-k][j-k]+calc(i-k+1,j))\\calc(l,r)=|b_l-a_r|+|b_r-a_l|\]这样的dp是\(O(n^2k)\)的,而\(1\leqk......
  • cf 1601B Frog Traveler Codeforces Round 751 (Div. 1)
     Problem-1601B-Codeforces BFS然后每次上升可以的范围是一个区间,然后每次都遍历这个区间的所有点,那么超时。用set等方式,合并这些区间,之前没遍历过的范围才更新(加入BFS需要遍历的队列里)。但是区间的更新特别容易写错…… 我的代码和造数据1/**2记录两个vi......
  • Codeforces Round 940 (Div. 2) and CodeCraft-23 题解
    CodeforcesRound940(Div.2)andCodeCraft-23题解题目链接A.Stickogon贪心#include<bits/stdc++.h>usingnamespacestd;#definefffirst#definesssecond#definepbpush_back#defineall(u)u.begin(),u.end()#defineendl'\n'#definedebu......