[算法训练] vp1096Div3-①C-构造②D-贪心+构造③E-贪心

2873 字
14 分钟
[算法训练] vp1096Div3-①C-构造②D-贪心+构造③E-贪心

vp1096Div3-①C-构造②D-贪心+构造③E-贪心#

https://www.wolai.com/7p7nhwBg6LJwyYg1Vi1MPt

Dashboard - Codeforces Round 1096 (Div. 3) - Codeforces

A-数学#

题目#

尤塞夫位于坐标 (0,0)(0, 0) 处,想要到达位于 (x,y)(x, y) 处的 Koshary 板块。

为了到达那里,尤塞夫需要迈很长的步子。从任意一点 (a,b)(a, b) 出发,迈出长长的一步,他就会到达:

  • (a+2,b)(a + 2, b)(a,b+2)(a, b + 2)

然而,尤塞夫在整个旅程中最多只能迈出***步。一小步移动到

  • (a+1,b)(a + 1, b)(a,b+1)(a, b + 1)

尤塞夫能到达科沙里板块的准确坐标 (x,y)(x, y) 吗?

数学#

可以发现,只能向左或向右走一次,则x和y不能同时为奇数

数学
for _ in range(int(input())):
x,y = map(int,input().split())
if x % 2 == 1 and y % 2 == 1:
print("NO")
else:
print("YES")

B-贪心#

题目#

优素福给了你一个长度为 nn 的序列 ss ,其中只有字符” (\texttt{(} “和” )\texttt{)} “。您最多**次可以执行以下操作:

  • 选择 ss 的子串 ^{\text{∗}} 并将其删除。然后,您可以将删除的字符逐个重新插入剩余的字符串中。每个字符都可以放在任意位置,与其他字符无关。

优素福想让你判断是否有可能在执行最多一次操作后得到一个正则括号序列 ^{\text{†}}

^{\text{∗}} 子串是字符串的连续子段。例如,“acab “是 “abacaba “的子串(从位置 33 开始,到位置 66 结束),但 “aa “或 “d “不是这个字符串的子串。因此,从位置 ll 到位置 rr 的字符串 ss 的子串是 s[l,r]=slsl+1srs[l, r]= s_l s_{l+1} \dots s_r

^{\text{†}} 正则括号序列是指可以通过在序列的原始字符之间插入字符 11++ 将其转换为正确算术表达式的括号序列。例如

  • 括号序列 ()()\texttt{()()}(())\texttt{(())} 是正则表达式(得到的表达式为: (1)+(1)\texttt{(1)+(1)}((1+1)+1)\texttt{((1+1)+1)} );
  • 括号序列 )(\texttt{)(}(\texttt{(})\texttt{)} 则不是。

贪心#

直接选择整个字符串是无害的

那么显然只需要统计字符串中”(“的数量是否等于”)“即可

贪心
from collections import Counter
for _ in range(int(input())):
n = int(input())
s = input().strip()
cnt = Counter(s)
ok = 1
if cnt['('] != cnt[')']:
ok = 0
if ok:
print("YES")
else:
print("NO")

C-构造#

题目#

Yousef 给了你一个由 nn 个正整数组成的数组 aa

f(a)f(a) 表示 aa 的子数组 ^{\text{∗}}能被 66 整除的个数。

更正式地说,对于 llrr 中的每一对索引 1lrn1 \le l \le r \le n ,考虑子数组 al,al+1,,ara_l, a_{l+1}, \dots, a_r 。如果这个子数组的元素乘积能被 66 整除,那么这个子数组就被计算在内。

例如,如果 a=[1,6,2]a = [1, 6, 2] ,那么乘积能被 66 整除的子数组有 [6][6][1,6][1, 6][6,2][6, 2][1,6,2][1, 6, 2] ,所以是 f(a)=4f(a) = 4

你的任务是对数组 aa 中的元素重新排序,使 f(a)f(a) 最小。如果有多种方法可以做到这一点,你可以输出其中任何一种。

^{\text{∗}} 如果从数组 aa 中删除开头的几个(可能是零个或全部)元素和结尾的几个(可能是零个或全部)元素,就可以得到数组 bb ,那么数组 bb 就是数组 aa 的子数组。

构造#

如果一个子数组同时包含 2233 这两个质因数,那么它的积就能被 66 整除。因此,每个元素都属于 44 组:

  • S6S_6 :能被 66 整除的元素
  • S2S_2 :能被 22 整除但不能被 33 整除的元素
  • S3S_3 :能被 33 除尽但不能被 22 除尽的元素
  • S1S_1 :既不能被 22 也不能被 33 整除的元素

S6S_6 中的元素已经使任何子数组都能被 66 整除,因此我们希望将这些元素集中在一个末尾,以尽量减少它们包含在子数组中的个数。在接下来的两段中,我们先不讨论 S6S_6

对于其余的元素,如果我们将所有的 S2S_2 放在所有的 S3S_3 之前,那么唯一可分割的子数组就是以 S2S_2 开始、以 S3S_3 结束的数组。任何其他顺序都只会产生更多这样的子数组。

我们该如何处理 S1S_1 中的元素呢?将它们放置在任何位置都不会减少可整除子数组的数量,但是我们可以将它们放置在 S2S_2S3S_3 之间,从而避免增加可整除子数组的数量。可被 66 整除的子数组仍然是以 S2S_2 为起点,以 S3S_3 为终点的数组,因此可整除的子数组数仍然尽可能少。

因此,最佳的构造是

[S6]+[S2]+[S1]+[S3][S_6] + [S_2] + [S_1] + [S_3]

构造
for _ in range(int(input())):
n = int(input())
a = list(map(int,input().split()))
x = []
y = []
z = []
p = []
for c in a:
if c % 6 == 0:
x.append(c)
elif c % 2 == 0:
y.append(c)
elif c % 3 == 0:
z.append(c)
else:
p.append(c)
ans = x + y + p + z
print(*ans)

D-贪心+构造#

题意#

优素福给了你一个由 2n2n 个整数组成的数组 aa 。在数组中,每个整数 x[0,n1]x \in [0, n - 1] 都会出现**次。

你的任务是找到一个子数组 al,al+1,,ara_l, a_{l + 1}, \dots, a_r ,它是一个回文数组 ^{\text{∗}} ,使得它的 mex(al,al+1,,ar)\operatorname{mex}(a_l, a_{l + 1}, \dots, a_r) ^{\text{†}} 最大。输出这个最大可能值。

{例如,字符串 “z”、“aaa”、“aba”、“abccba “是回文字符串,但字符串 “codeforces”、“reality”、“ab “不是回文字符串。

^{\text{†}} 整数数组的 mex\operatorname{mex} (最小排除数)定义为数组中不出现的最小非负整数。例如

  • [2,2,1][2,2,1]mex\operatorname{mex}00 ,因为 00 不属于数组。
  • [3,1,0,1][3,1,0,1]mex\operatorname{mex}22 ,因为 0011 属于数组,而 22 不属于数组。
  • [0,3,1,2][0,3,1,2]mex\operatorname{mex}44 ,因为 00112233 属于数组,而 44 不属于数组。

贪心+构造#

最优子数组总是包含 00 元素。这是因为,如果它不包含 00 ,那么就是 mex=0\operatorname{mex} = 0 。但是 mex=1\operatorname{mex} = 1 可以通过将 00 元素视为自己的子数组来实现。因此,最佳子数组必须始终包含 00 元素。

这样,我们就只有 33 个候选中心可以展开回文。假设 xx 是数组中第一个 00 的位置, yy 是第二个 00 的位置:

  • xx 可以是最佳宫位的中心。
  • yy 可以是最佳宫位的中心。
  • xxyy 可以位于首尾对应的两边。例如, [,0,,j,k,j,,0,][\dots, 0, \dots, j, k, j, \dots, 0, \dots] 为奇数宫位, [,0,,j,k,k,j,,0,][\dots, 0, \dots, j, k, k, j, \dots, 0, \dots] 为偶数宫位。

我们可以尝试上述每一种选择,只要子数组仍然是回文数组,就从候选中心开始扩展,同时跟踪 mex\operatorname{mex} 。答案就是所有 33 候选数中最大的 mex\operatorname{mex}

贪心+构造
def f(l,r,n,v):
s = set(range(n+1))
while l >= 0 and r < 2 * n and v[l] == v[r]:
if v[l] in s:
s.discard(v[l])
l -= 1
r += 1
return min(s)
for _ in range(int(input())):
n = int(input())
v = list(map(int,input().split()))
x = y = -1
for i in range(2*n):
if v[i] == 0:
if x == -1:
x = i
else:
y = i
ans = max(f(x,x,n,v),f(y,y,n,v),f((x + y)//2,(x + y + 1)//2,n,v))
print(ans)

E-贪心#

问题#

优素福有 nn 列并排的立方体。第 ii 列中有 aia_i 个相同的单位立方体垂直堆叠在一起。最初,重力将立方体向下拉,因此每列 ii 中都有 aia_i 个立方体,高度为 1,2,,ai1, 2, \dots, a_i

突然,重力向右移动。每个立方体都尽可能向右水平滑动。一个立方体不能穿过其他立方体,也不能与其他立方体重叠,它必须保持原来的高度。最终的配置是由初始高度唯一决定的。

在重力移动之前,优素福最多可以进行***次操作:选择索引 ii 并将 aia_i 减少 11 (即从该列中移除一个立方体)。他也可以选择什么都不做。

如果一个立方体在重力移动后的列索引与原来的列索引不同,则称该立方体移动了。

假设尤塞夫最优化地使用了单次减少(或选择不使用),求重力移动后移动的立方体的最大可能数量。

AI题解#

t = int(input())
for _ in range(t):
n = int(input())
v = list(map(int, input().split()))
total = sum(v)
# 后缀最小值
suf_mn = [0] * n
suf_mn[n - 1] = v[n - 1]
total -= suf_mn[n - 1]
for i in range(n - 2, -1, -1):
suf_mn[i] = min(suf_mn[i + 1], v[i])
total -= suf_mn[i]
# 统计最长的连续相等段
mx = -1
cur = 1
for i in range(1, n):
if suf_mn[i] == suf_mn[i - 1]:
cur += 1
else:
mx = max(mx, cur)
cur = 1
mx = max(mx, cur)
print(total + mx - 1)


一、问题理解#

  • n 列立方体,第 i 列有 a[i]
  • 重力向右移动:每个立方体尽量向右滑动,保持高度不变,不能重叠
  • 可以至多一次减少某列的一个立方体
  • 移动的立方体的最大数量

二、重力向右后的形态#

重力向右移动后,立方体会形成”台阶”状:

  • 高度从右往左非递减
  • 最终每列的高度 = 后缀最小值
原始:[5, 3, 4, 2, 1]
后缀最小值:[1, 1, 1, 1, 1](太极端)
实际:每个位置最多能保留 min(原始高度, 右侧的最小高度)

三、核心公式#

重力移动后,第 i 列的高度 = min(v[i], v[i+1], ..., v[n-1]) = 后缀最小值

不动立方体总数 = 所有列后缀最小值之和

移动立方体数 = 总立方体 - 不动立方体


四、操作:减少一个立方体#

可以选择某列减少 1。减少哪一列能让移动数最大?

  • 减少某列,可能降低该列的后缀最小值
  • 如果该列的后缀最小值等于它自己,减少它会让后缀最小值减 1,从而让更多立方体移动

五、代码逻辑#

total = sum(v) # 总立方体数
# 计算后缀最小值之和
suf_mn[i] = min(suf_mn[i+1], v[i])
total -= suf_mn[i] # 减去不动立方体

total 现在 = 不操作时的移动立方体数


# 统计最长连续相等段
mx = -1
cur = 1
for i in range(1, n):
if suf_mn[i] == suf_mn[i-1]:
cur += 1
else:
mx = max(mx, cur)
cur = 1

为什么统计连续相等段?

如果 suf_mn[i] = suf_mn[i+1] = ... = suf_mn[j],说明这些列的后缀最小值相同,且等于某列的实际值。

减少那个”卡住”的列(即 v[k] == suf_mn[k] 的那一列),可以让整个连续段的后缀最小值减 1,从而让这段所有列的立方体多移动 1 个。


六、最终答案#

print(total + mx - 1)
  • total:不操作的移动数
  • mx - 1:操作一次能让多少个额外立方体移动
  • 合起来就是最大移动数

七、图解#

v = [5, 3, 4, 2, 1]
后缀最小值:
suf_mn = [1, 1, 1, 1, 1]
不动的 = 1+1+1+1+1 = 5
移动的 = (5+3+4+2+1) - 5 = 10
连续相等段:5个1连续,mx = 5
操作:减少 v[0] 5→4,后缀最小值变成 [1,1,1,1,1](不变?)
等等...

实际上,如果 v[4]=1suf_mn[4]=1,减少它:

  • suf_mn 变成 [0,0,0,0,0]
  • 不动的 = 0,移动的 = 全部

但一次操作只能减少 1,所以移动数增加 连续相等段长度 - 1


八、总结#

步骤内容
1计算后缀最小值数组
2不操作时的移动数 = 总和 - 后缀最小值之和
3找最长连续相等后缀最小值段
4答案 = 不操作移动数 + 最长连续段 - 1

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

[算法训练] vp1096Div3-①C-构造②D-贪心+构造③E-贪心
http://blog.7a7a68.xyz/posts/vp1096div3-c-构造d-贪心构造-e-贪心/
作者
JulY
发布于
2026-07-21
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
JulY
愿你明日如绚丽之花.
公告
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
76
分类
4
标签
20
总字数
160,526
运行时长
0
最后活动
0 天前

目录