Codeforces Round 935 (Div. 3) A~F (Go语言题解)
A - Setting up Camp
题目:组委会计划在游览结束后带领奥林匹克运动会的参赛选手进行徒步旅行。目前,正在计算需要搭乘的帐篷数量。据了解,每个帐篷最多可容纳 3 3 3 人。
在参赛选手中,有 a a a 名内向型选手, b b b名外向型选手和 c c c名综合型选手:
- 每个内向的人都想独自住在帐篷里。因此,内向者的帐篷里必须只有一个人–只有内向者自己。
- 每个外向者都希望与另外两个人住在一个帐篷里。因此,一个外向者的帐篷里必须正好有三个人。
- 每个人都可以选择任何一种方式(独居、与他人同住或与他人同住)。
组委会非常尊重每位参赛者的意愿,因此他们希望满足所有参赛者的愿望。
请告诉我们至少需要多少顶帐篷,以便所有参加者都能根据自己的喜好找到住处。如果无法满足所有参与者的愿望,则输出 − 1 -1 −1 。
题解 :判断3 - b % 3 是否大于c如果大于则不可能安排,其余结果都是可行的最小的即为下面代码所示,时间复杂度 O ( 1 ) O(1) O(1)
func solve() {
in := bufio.NewReader(os.Stdin)
out := bufio.NewWriter(os.Stdout)
defer out.Flush()
var t int
for Fscan(in, &t); t > 0; t-- {
var a, b, c int
Fscan(in, &a, &b, &c)
ans := a + b/3
b %= 3
if b > 0 && 3-b > c {
Fprintln(out, -1)
continue
} else {
if b != 0 {
ans++
c -= 3 - b
}
ans += c / 3
if c%3 > 0 {
ans++
}
}
Fprintln(out, ans)
}
}
B - Fireworks
题目:徒步旅行的其中一天恰逢节假日,因此决定晚上在营地安排一场节日焰火表演。为此,徒步旅行的组织者购买了两个烟花发射装置和大量的发射炮弹。
两个装置同时开启。第一个装置每隔 a a a 分钟(即发射后 a , 2 ⋅ a , 3 ⋅ a , … a, 2 \cdot a, 3 \cdot a, \dots a,2⋅a,3⋅a,… 分钟)发射一次烟花。第二个装置每 b b b 分钟(即发射后 b , 2 ⋅ b , 3 ⋅ b , … b, 2 \cdot b, 3 \cdot b, \dots b,2⋅b,3⋅b,… 分钟)发射一次烟花。
每个烟花在发射后的 m + 1 m + 1 m+1 分钟内都可以在天空中看到,也就是说,如果一个烟花是在装置开启后的 x x x 分钟后发射的,那么从 x x x 到 x + m x + m x+m (包括首尾两分钟)的每一分钟都可以看到该烟花。如果一个烟花在另一个烟花 m m m 分钟后发射,则两个烟花都将在一分钟内可见。
天空中最多可以同时看到多少枚烟花?
题解:可以看到的最多烟花数量是两种烟花同时在天空中存在的最多数量之和即为 m / a + 1 + m / b + 1 m / a + 1 + m / b + 1 m/a+1+m/b+1 时间复杂度 O ( 1 ) O(1) O(1)
func solve() {
in := bufio.NewReader(os.Stdin)
out := bufio.NewWriter(os.Stdout)
defer out.Flush()
var t int
for Fscan(in, &t); t > 0; t-- {
var a, b, m int
Fscan(in, &a, &b, &m)
Fprintln(out, m/a+1+m/b+1)
}
}
C - Left and Right Houses
题目:莱托沃村有 n n n 栋房子。村民们决定修建一条大路,将村子分为左右两边。每个居民都想住在街道的右侧或左侧,这可以用序列 a 1 , a 2 , … , a n a _ 1, a _ 2, \dots, a _ n a1,a2,…,an 来描述,其中 a j = 0 a _ j = 0 aj=0 如果 j j j 房子的居民想住在街道的左侧;否则, a j = 1 a _ j = 1 aj=1 。
道路将从两栋房子之间穿过。它左边的房子将被宣布为左侧,右边的房子将被宣布为右侧。更正式地说,让道路从房屋 i i i 和 i + 1 i+1 i+1 之间通过。那么位于 1 1 1 和 i i i 之间的房屋将位于街道的左侧,位于 i + 1 i+1 i+1 和 n n n之间的房屋将位于街道的右侧。在这种情况下,整个村庄将分别被宣布为右侧或左侧。
为了使设计公平,我们决定在铺设道路时,至少要让村子两边各一半的居民对选择感到满意。也就是说,在一侧的 x x x 个居民中,至少有 ⌈ x 2 ⌉ \lceil\frac{x}{2}\rceil ⌈2x⌉ 个居民愿意住在这一侧,其中 ⌈ x ⌉ \lceil x \rceil ⌈x⌉ 表示四舍五入的实数 x x x 。
在路的左边,会有 i i i 座房子,在相应的 a j a _ j aj 中,至少要有 ⌈ i 2 ⌉ \lceil\frac{i}{2}\rceil ⌈2i⌉ 个零。路的右边有 n − i n-i n−i 座房子,在相应的 a j a _ j aj 中至少要有 ⌈ n − i 2 ⌉ \lceil\frac{n-i}{2}\rceil ⌈2n−i⌉ 个一。
确定在哪座房子 i i i 之后铺设道路,以满足所述条件,并尽可能靠近村庄中央。从形式上看,在所有合适的位置 i i i 中,尽量减少 ∣ n 2 − i ∣ \left|\frac{n}{2} - i\right| 2n−i 。
如果有多个合适的位置,输出较小的一个。
题解:计算前后缀然后枚举可能的位置,通过前后缀判断是否满足题目所述即可,具体如下所示,时间复杂度 O ( n ) O(n) O(n)
func solve() {
in := bufio.NewReader(os.Stdin)
out := bufio.NewWriter(os.Stdout)
defer out.Flush()
var t int
for Fscan(in, &t); t > 0; t-- {
var n int
Fscan(in, &n)
s := make([]byte, n)
Fscan(in, &s)
pre := make([]int, n+1)
suf := make([]int, n+1)
for i := 1; i <= n; i++ {
if s[i-1] == '1' {
pre[i] = pre[i-1] + 1
} else {
pre[i] = pre[i-1]
}
}
for i := n - 1; i >= 0; i-- {
if s[i] == '1' {
suf[i] = suf[i+1] + 1
} else {
suf[i] = suf[i+1]
}
}
ans := -1
for i := 0; i <= n; i++ {
if (i-pre[i]) >= i/2+i%2 && suf[i] >= (n-i)/2+(n-i)%2 {
if abs(n-i*2) < abs(n-ans*2) {
ans = i
}
}
}
Fprintln(out, ans)
}
}
D - Seraphim the Owl
题目:大家排成 n n n 号队,从 i = 1 i = 1 i=1 号开始,向猫头鹰谢拉菲姆请教生命的意义。不巧的是,基里尔正忙着为这个问题编写传说,所以他来得晚了一些,站在了 n n n 号人之后的队尾。基里尔对这种情况非常不满,于是他决定贿赂一些排在他前面的人。
对于队列中的 i i i \th人,基里尔知道两个值: a i a _ i ai 和 b i b _i bi : a i a _ i ai 和 b i b _i bi 。如果此刻基里尔站在位置 i i i 上,那么他可以选择任意一个位置 j j j ,比如 i i i ,然后与位置 j j j 上的人交换位置。在这种情况下,基里尔必须支付他 a j a _ j aj 个硬币。而对于 j < k < = i j < k <= i j<k<=i 的每一个 k k k ,基里尔都要向位置 k k k 的人支付 b k b _ k bk 个硬币。
基里尔很节俭,所以他想花尽可能少的硬币,但是他又不想等太久,所以基里尔认为他应该排在第一个 m m m 人中。
请帮助基里尔确定,为了不等太久,他最少需要花费多少金币。
题解:动态规划问题根据题目可以得到状态转移方程是 i < m 时 f [ i ] = m i n ( a [ i ] , f [ i − 1 ] ) , i > = m 时 f [ i ] = m i n ( f [ i − 1 ] + a [ i ] , f [ i − 1 ] + b [ i ] ) i < m 时 f[i] = min(a[i], f[i - 1]), i >= m 时 f[i] = min(f[i - 1] + a[i], f[i - 1] + b[i]) i<m时f[i]=min(a[i],f[i−1]),i>=m时f[i]=min(f[i−1]+a[i],f[i−1]+b[i])
具体如下述代码所示,代码展示的时记忆化搜索的过程,时间复杂度 O ( n ) O(n) O(n)
func solve() {
in := bufio.NewReader(os.Stdin)
out := bufio.NewWriter(os.Stdout)
defer out.Flush()
var t int
for Fscan(in, &t); t > 0; t-- {
var n, m int
Fscan(in, &n, &m)
a := make([]int, n)
b := make([]int, n)
for i := range a {
Fscan(in, &a[i])
}
for i := range b {
Fscan(in, &b[i])
}
f := make([]int, n)
for i := range f {
f[i] = -1
}
var dfs func(int) int
dfs = func(i int) int {
if i == 0 {
return a[0]
}
if f[i] != -1 {
return f[i]
}
res := 0
defer func() { f[i] = res }()
if i < m {
res = min(a[i], dfs(i-1)+b[i])
return res
}
res = min(dfs(i-1)+a[i], dfs(i-1)+b[i])
return res
}
Fprintln(out, dfs(n-1))
}
}
E - Binary Search
题目:安东在徒步旅行中感到无聊,想解决一些问题。他问基里尔有没有新问题,基里尔当然有。
给你一个大小为 n n n 的排列数 p p p 和一个需要求出的数字 x x x 。长度为 n n n 的排列是由 n n n 个不同的整数组成的数组,这些整数从 $ 1 1 1$ 到 n n n 依次排列。例如, [ 2 , 3 , 1 , 5 , 4 ] [2,3,1,5,4] [2,3,1,5,4] 是一个排列数组,但 [ 1 , 2 , 2 ] [1,2,2] [1,2,2] 不是排列数组( 2 2 2 在数组中出现了两次), [ 1 , 3 , 4 ] [1,3,4] [1,3,4] 也不是排列数组( n = 3 n=3 n=3 但数组中有 4 4 4 )。
你认为自己是一个很酷的程序员,所以你将使用一种高级算法进行搜索–二进制搜索。但是,你忘了二进制搜索必须对数组进行排序。
为了得到正确的答案,你可以在运行算法之前执行以下不超过 2 2 2 次的操作:选择索引 i i i , j j j ( 1 ≤ i , j ≤ n 1\le i, j \le n 1≤i,j≤n ) 并交换位置 i i i 和 j j j的元素。
然后,执行二进制搜索。在算法开始时,声明两个变量 l = 1 l = 1 l=1 和 r = n + 1 r = n + 1 r=n+1 。然后执行下面的循环:
- 如果 r − l = 1 r - l = 1 r−l=1 ,结束循环
- m = ⌊ r + l 2 ⌋ m = \lfloor \frac{r + l}{2} \rfloor m=⌊2r+l⌋
- 如果 p m ≤ x p _ m \le x pm≤x ,赋值 l = m l = m l=m ,否则 r = m r = m r=m。
我们的目标是在算法之前重新排列排列组合中的数字,以便在算法执行之后, p l p _ l pl 等于 x x x 。可以证明 2 2 2 操作总是足够的。
题解:这道题目本质是构造题目,一个比较简单的思路便是将最后找出的 l l l与x原本所在位置index进行呼唤即可, 时间复杂度 O ( n ) O(n) O(n)
上述构造思路如下,当 p m ≤ x p _ m \le x pm≤x 时赋值给 l = m l = m l=m 所以将x与最后所找到 l l l 互换位置是不影响查找过程的。而且由于p是一个排列不存在重复的问题,所以上述构造是正确的。
func solve() {
in := bufio.NewReader(os.Stdin)
out := bufio.NewWriter(os.Stdout)
defer out.Flush()
var t int
for Fscan(in, &t); t > 0; t-- {
var n, x int
Fscan(in, &n, &x)
p := make([]int, n)
ans := -1
for i := range p {
Fscan(in, &p[i])
if p[i] == x {
ans = i
}
}
l, r := 0, n
flag := make([]bool, n)
for l+1 < r {
mid := l + (r-l)>>1
flag[mid] = true
if p[mid] <= x {
l = mid
} else {
r = mid
}
}
if l == ans {
Fprintln(out, 0)
} else {
Fprintln(out, 1)
Fprintln(out, min(l+1, ans+1), " ", max(l+1, ans+1))
}
}
}
F - Kirill and Mushrooms
题目:营地里的人一睡着,基里尔就偷偷溜出帐篷,去智者橡树那里采蘑菇。
众所周知,橡树下生长着 n n n 种蘑菇,每种蘑菇都有魔力 v i v _ i vi 。基里尔非常想用这些蘑菇制作一种魔力最大的灵药。
灵药的强度等于其中蘑菇的数量与这些蘑菇中最小魔力的乘积。要配制灵药,基里尔要依次采摘生长在橡树下的蘑菇。基里尔可以按照任何顺序采集蘑菇。
然而,事情并非如此简单。智慧橡树告诉基里尔从 1 1 1 到 n n n 的数字 p p p 的排列组合。如果基里尔只采摘 k k k 个蘑菇,那么所有指数为 p 1 , p 2 , … , p k − 1 p _ 1, p _ 2, \dots, p _ {k - 1} p1,p2,…,pk−1 的蘑菇的魔力就会变成 0 0 0 。基里尔不会使用魔力为零的蘑菇来配制灵药。
你的任务就是帮助基里尔采集蘑菇,使他能够酿造出最大魔力的灵药。不过,基里尔有点害怕在橡树附近逗留太久,所以在所有合适的采集蘑菇的选项中,他要求您找到蘑菇数量最少的那个。
长度为 n n n 的排列是由 1 1 1 到 n n n 之间的 n n n 个不同整数按任意顺序排列而成的数组。例如, [ 2 , 3 , 1 , 5 , 4 ] [2,3,1,5,4] [2,3,1,5,4] 是一个排列数组,但 [ 1 , 2 , 2 ] [1,2,2] [1,2,2] 不是排列数组( 2 2 2 在数组中出现两次), [ 1 , 3 , 4 ] [1,3,4] [1,3,4] 也不是排列数组( n = 3 n=3 n=3 ,但 4 4 4 在数组中出现)。
题解:注意看题目,当时比赛时看错了 p i p_i pi的意思, p i p_i pi时的值是数据V的下标。因为要最大化魔力值且拿蘑菇的数量最少,所以可以枚举蘑菇数量 1 , … , n 1,\dots,n 1,…,n, 下面代码用数组a表示上述题目中所说的v。因为要最大化魔力值所以需要在选取相同蘑菇数量下 a i a_i ai的最小值越大越好,所以可以用大顶堆维护,并且使用map记录根据当前数量已经被淘汰的 p i p_i pi作为懒惰标记,然后将其从堆中懒删除。使用一个数last维护当前所取出的蘑菇的最小值,其于数量 i i i 相乘不断进行跟新答案即可。
func solve() {
in := bufio.NewReader(os.Stdin)
out := bufio.NewWriter(os.Stdout)
defer out.Flush()
var t int
for Fscan(in, &t); t > 0; t-- {
var n int
Fscan(in, &n)
a := make([]int, n)
b := make([]int, n)
h1 := hp1{}
for i := range a {
Fscan(in, &a[i])
heap.Push(&h1, a[i])
}
for i := range b {
Fscan(in, &b[i])
}
mp := map[int]int{}
ans, cnt, acnt, last := 0, 0, 0, 0 //cnt表示当前已经选取的蘑菇数量
//last表示已经选了的蘑菇中的最小值
for i := 0; i < n; i++ {
for h1.Len() > 0 && cnt <= i {
if mp[h1.IntSlice[0]] > 0 { //当前懒惰标记生效将其删除
mp[heap.Pop(&h1).(int)]--
} else { //当前值便是选出的最小值
last = heap.Pop(&h1).(int)
cnt++
}
}
if cnt <= i { //剩余的蘑菇数量不会大于cnt了直接退出
break
}
if (i+1)*last > ans { //更新答案
ans = (i + 1) * last
acnt = i + 1
}
if a[b[i]-1] >= last { //当前位置p_i是最小值下一步必被移除,所以cnt--
cnt--
} else {
mp[a[b[i]-1]]++
}
}
Fprintln(out, ans, acnt)
}
}
type hp1 struct{ sort.IntSlice }
func (h hp1) Less(i, j int) bool { return h.IntSlice[i] > h.IntSlice[j] } // 加上这行变成最大堆
func (h *hp1) Push(v any) { h.IntSlice = append(h.IntSlice, v.(int)) }
func (h *hp1) Pop() any { a := h.IntSlice; v := a[len(a)-1]; h.IntSlice = a[:len(a)-1]; return v }