周总结2022.1.31-2022.2.6

1.31 

Codeforces

Codeforces Round #769 (Div. 2)

A - ABC (0:07, +1)

可恶!可恶!可恶!读错题了!读错题了!读错题了!

只要给定的01串长度大于2,就一定是NO,如果长度是2,如果01串里的两位数字相同,则为NO。其余情况全是YES(好像也没几种情况了)。

AC代码

B - Roof Construction (0:20, +)

这个题挺唬人的,不过推一推就可以发现,其实就是把不超过给定数字的最大的2的幂数标记,然后从1~n按照顺序输出,如果要输出那个被标记的数了,就先输出个0,然后再把那个数输出。

AC代码

C - Strange Test (0:58, +)

以为是个什么大难题,然后发现,一旦异或就会导致a \geq b。所以实际上,异或只会执行一次。那么枚举异或之前a所相加的次数、b所相加的次数就好了。

AC代码

D - New Year Concert

赛中想了个拆因数+线段树,但是因为拆因数写成了质因数分解,线段树还写炸了,导致没来得及交上去。(算了算时间复杂度,会T)

事实上,拆因数本身是正确的,但是时间复杂度很不优秀。正确做法是二分。因为我们注意到,随着长度的增加,gcd本身是在逐渐减小的,所以gcd和长度的交点有且仅有一个。所以我们先建立一个gcd的线段树,然后二分查找gcd与长度相等的点。由于我们每次可以将不合法的数字换成一个超级大的数字,查找后续的数字时,那个数即以前的所有数字都可以不再考虑了。如果这个数中找到了gcd和长度相等的位置,那么这个数不合法,令答案+1,并记录当前位置,下次二分的时候以这个数的后一个数作为左端点。

AC代码

2.1

Nowcoder

21303 - 删括号

给两个合法的字符串s,t,问能否从s中删去0个或多个"()",使s=t。

有两个做法:DP方法和把括号序列移到树上的方法。

DP方法:

对于串的常规匹配模式:dp[i][j]表示第一个串前i个字母和第二个串前j个字母是否匹配。而这道题目由于我们可以主动删去t串的一些字符,所以我们还要s串中的k个字符。而事实上,我们只需要考虑删掉'(',因为题目保证括号序列一定合法,所以我们遇到的左括号一定是多于右括号的。此时我们只需要记录,删去几个已经读到的左括号,使t串前j个与s串前i个匹配。

所以我们定义dp[i][j][k]表示s串的前i个符号、t串的前j个符号,在删除掉s串中的k个左括号后是否匹配,如果匹配值为1,不匹配值为0。

初态,一个字母都没有读入,此时i=j=k=0。这个状态是合法的。即dp[0][0][0] = 1。

现在我们考虑转移方程。我们假设dp[i][j][k]=1,以此可以匹配的状态读取s串的第i+1个字符和t串的j+1个字符。显然现在有四种情况:

1.s[i + 1] = '(' and t[i + 1] = '('

2.s[i + 1] = '(' and t[i + 1] = ')'

3.s[i + 1] = ')' and t[i + 1] = '('

4.s[i + 1] = ')' and t[i + 1] = ')'

分别来考虑:

当s[i + 1] = '(' and t[i + 1] = '('时:由于两个串下一个符号相同,且所以不难推出k=0时,dp[i + 1][j + 1][0] = 1。如果我们只看s串的下一个符号,那么想要让s的前i+1个符号和t的前j个符号匹配,那就只能多删除s串的新读入的那个左括号了,于是得出:dp[i + 1][j][k + 1] = 1。

为什么要在k=0的情况下?

由于s和t都是合法的,所以读入的左括号的数量一定大于等于右括号。而当k>0时,意味着dp[i][j]所表示的配对中,有大括号被删去了。而我们题目中的要求是必须要删除匹配的一个左括号和一个右括号。我们当前的表示是只删除了左括号,而不能保证后面有足够的右括号让我们删除。

当s[i + 1] = '(' and t[i + 1] = ')'时:由于两个串的下一个符号不同,肯定不能同时匹配。由于s[i + 1] = '(',所以可以想到,和第一种情况一样,我们读入但删去s[i + 1]的左括号,即令dp[i + 1][j][k + 1] = 1。

当s[i + 1] = ‘)' and t[i + 1] = '('时:当k>0时,因为这两个不一样,所以肯定不能在i+1的位置上同时匹配。所以考虑s的前i+1个字符和t的前i个字符匹配。那么,s的右括号可以让前面一个被删除的左括号复原,然后成对移走,即dp[i + 1][j][k - 1] = 1。

当s[i + 1] = ')' and t[i + 1] = ')'时:和第一种情况同理,当k=0时,有dp[i + 1][j + 1][0] = 1。和第三种情况同理,考虑s的前i+1个字符和t的前i个字符匹配,即dp[i + 1][j][k - 1] = 1。

把以上四种情况的转移方程做一个总结,写出代码如下:

if (k == 0 and s[i + 1] == t[j + 1]) {
    dp[i + 1][j + 1][0] = 1;
}
    if (s[i + 1] == '(') {
        dp[i + 1][j][k + 1] = 1;
}
else if (k) dp[i + 1][j][k - 1] = 1;

 AC代码

括号序列的树表示法

我们用树来存储括号内外嵌套的一个关系。举一个直观的例子:(()()())。我们用树可以表示成:

 整个空间用1号结点表示(其实没有也行)

2号结点:(()()())

3号结点:(()()())

4号结点:(()()())

5号结点:(()()())

在外层的那对括号表示的结点在里层括号所表示的结点的祖先一侧。

我们用这段代码来把括号序列存起来。

我们需要一个vector来存每一个结点的子结点,用一个栈来维护当前正在处理哪些括号的内部。

大体的思路就是:遇到一个左括号,意味着内部嵌套的这一层开始了,于是给这个括号对标个序号,然后加入到当前栈顶的子结点中,不难理解。然后把这个括号对推入栈中,表示这个括号对的内部嵌套从此开始。遇到一个右括号,则意味着当前所管辖的左括号任务结束,将其从栈顶弹出。

代码:

s.push(1);
for (LL i = 0; i < n; ++i) {
    if (s[i] == '(') {
        T[s.top()].push_back(++sid);
        s.push(sid);
    }
    else s.pop();
}

对于这道题目,我们要删除s串中的括号对。删除括号对,在这道题目当中,其实就是删除表示的这棵树的结点。所以我们要将给的s串和t串用树表示起来,然后看能否把s串的几个叶节点摘除,使剩下的树和t的树一模一样即可。

同时我们也注意到,由于嵌套顺序相似(否则没办法变成同样的树),加上我们建树时标号的顺序也就代表了当前结点的子结点的顺序,那么我们不考虑编号的情况下,只需要从左到右遍历,看看能不能让两个结点匹配。

具体的看看代码就看懂了。

AC代码

Codeforces

Educational Codeforces Round 122 (Rated for Div. 2)

A - Div. 7

当前数+0~+6内一定有一个可以被7整除的。

AC代码

B - Minority

长度为1和2时答案一定为0。

长度大于等于3时,统计1的个数和0的个数。如果两个数不一样多,取最少。如果两个数一样多,去那个数-1(抛去最边上一个数)

AC代码

C - Kill the Monster

最优做法一定是k枚硬币全都用掉。所以给第一个操作i次,一定给第二个操作k-i次。枚举i从0到k,如果有一种方法可以打败怪兽,就输出YES,否则输出NO。

AC代码

D - Make Them Equal

显然的01背包。赛中每一个数的代价统计出错,花费了很长时间。

人生首次被Hack。原因是dp数组过大,每次都进行了初始化,无比浪费时间。事实上,k根本到不了1e6,甚至最多也就15000,给15000初始化就足够了,时间上也搞得过去。

教训:时刻注意初始化,也要注意初始化的时间复杂度!如果无法优化,就观察是否真的有必要整个数组都初始化,或求出初始化的边界。

AC代码

2.2

Codeforces

Codeforces Round #723 (Div. 2)

A - Mean Inequality

当n=1时,直接把两个数输出出来即可。

其他情况中,我们可以先对整个数列排序,然后保持凹凸的形状,即构成高低高的样子。因为最后要考虑的是环状的,所以处理一下前端和后端,交换交换位置就可以了。

AC代码

B - I Hate 1111

很巧妙的题目。一开始没有想到,1111=11×101,11111=11×1000+111……所以,除了11和111,其他的都是没有用的。我们只需要不断地减掉111,每减一次看能否被11整除即可。

原来标题就是个很好的提示。

AC代码

C1 - Potions (Easy Version)

C1也可以用dp来实现。考虑到体力值很大,所以我们不把体力值作为一个参数来转移。那么我们要考虑的参数就是走到了第几步、喝了几瓶药。令dp[i][j]表示走到第i步,喝了j瓶药后的最大体力。由于n最大值为2000,所以O(n^2)是完全ok的。

接下来考虑转移方程:

初态在第0个位置上,喝了0瓶药,体力是0,即dp[0][0]=0。然后我们令其他dp[i][j]=-1,表示我们无法转移到这个状态。

我们从可以转移达到的dp[0][0]开始依次求解dp数组。假设当前dp[i][j]可以被转移,且dp[i][j]>=0,那么考虑第i+1瓶能不能喝。如果我喝完之后体力值小于0,如果dp[i + 1][j + 1]没有被转移到过,就继续保持-1,否则不动。如果喝完之后体力值>0,那么dp[i + 1][j + 1] = max(dp[i + 1][j + 1], dp[i][j] + a[i + 1])。当然,如果这个药可以补充体力,那么一定喝掉。如果这个药会减少体力,也可以有不喝的选择,那么dp[i + 1][j] = max(dp[i + 1][j], dp[i][j])。

最后在i=n处寻找值非负的最大的j,即为答案。

AC代码

C2 - Potions (Hard Version)

C1中的dp复杂度在C2中是不可行的,200000的大小,最坏也只能接受O(n\log n)

我们考虑贪心。很显然,我们要喝掉所有补充体力的药。所以我们只需要考虑那些扣除体力的药。

我们存储当前我们选择喝掉的药物。然后当我们往下走时,我们会遇到新的药。如果这个药是扣除体力的,我们先看喝掉之后会不会使体力变成负数。如果不能,直接喝掉,如果能,那就和我们当前所喝掉过的扣除体力最多的药进行对比,如果这个药比我们喝过的扣除体力最多的还要多,那就不接受,否则替换掉那个药。因为我们当前能做到的就是贪心地保持所喝的药最多的情况。所以如果我们不能喝,那就要维持这个数量不变。

可以用优先队列维护,时间复杂度刚好是O(n\log n)

AC代码

D - Kill Anton

这道题目告诉我们,要勇于尝试,勇于猜结论。可以想到,这道题目和逆序对有关联,所谓的恢复原状,就是求一个逆序对的数量。我们先把调整前的每一个字母打上id,从左到右依次为1,2,……,n。打乱顺序后,从左到右依次记录每一个数的id,得到一个数组。这个数组的逆序对数量,就是当前要调整回原来状态的数量。

通过手玩,我们可以发现,把相同字母放在一起,也可以达到所谓的移动次数最多,即不会使最终的最优答案变坏。

于是因为一共只有4种字母,枚举24种排列情况,分别求逆序对数目,然后求出最大值。时间复杂度O(24n\log n)

AC代码

2.3

Codeforces

Codeforces Round #751 (Div. 2)

A - Two Subsequences

找字符串里最小的一个字母,提出来做a,剩下的拼一起做b。(作……作弊?)

AC代码

B - Divine Array

可以发现,最多只能走n步,就不会发生变化了。所以暴力枚举n次,然后把答案存起来,问啥给啥就行了。询问超过n的按n处理。

AC代码

C - Array Elimination

拆位,发现能不能做到和每一位上1的个数是有关的。结合样例推一推,可以发现就是一个最大公约数问题。

AC代码

D - Frog Traveler

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值