Educational Codeforces Round 95题解
题目链接
代码链接
A. Buying Torches
题目大意:
你手上现在有一个木棍。有以下两种交换方式:
1.用一个木棍交换 x x x个木棍
2.用 y y y个木棍交换 1 1 1个煤
你现在想生产出 k k k个火把,每个火把需要 1 1 1个木棍和 1 1 1个煤,问最少完成多少次交换,才能生产出 k k k个火把
数据范围: 2 ≤ x ≤ 1 0 9 , 2 ≤ x ≤ 1 0 9 ; 1 ≤ y , k ≤ 1 0 9 2 \leq x \leq 10^9,2 \leq x \leq 10^9; 1 \leq y,k \leq 10^9 2≤x≤109,2≤x≤109;1≤y,k≤109
题解:
因为最后需要 k k k个火把,即需要 k k k个煤,所以需要 y × k y\times k y×k个木棍用于第二种交换方式
最后还需要 k k k个木棍用于做火把,所以一共需要 y × k + k y \times k + k y×k+k个木棍
每次使用第一种交换方式,手上的木棍会多出 x − 1 x-1 x−1个,所以第一种交换需要进行至少 ⌈ y × k + k x − 1 ⌉ \lceil \frac{y \times k + k}{x-1} \rceil ⌈x−1y×k+k⌉
第二种交换需要进行 k k k次,所以答案为$\lceil \frac{y \times k + k}{x-1} \rceil + k $
B. Negative Prefixes
题目大意:
给定一个长度为 n n n的序列,其中一些位置是固定的,你可以任意交换不固定的位置的数。要求最终序列的前缀和序列中,最后一个负值的下标最小。
数据范围: 1 ≤ n ≤ 100 , − 1 0 5 ≤ a i ≤ 1 0 5 ) 1≤n≤100,−10^5≤a_i≤10^5) 1≤n≤100,−105≤ai≤105)
题解:
设原序列为 a n a_n an
考虑交换两个未固定位置的两个数对于前缀和序列的影响。假设两个数的下标是 i i i和 j j j,那么前缀和序列中,下标比 i i i小的和比 j j j大的将不变。
而前缀和序列中下标在 i i i和 j j j之间的数
如果 a i > a j a_i>a_j ai>aj,则将全部减小,答案可能变劣,因为最后一个负值的下标一定是不变或者向后移动
如果 a i < a j a_i<a_j ai<aj,则将全部增大,答案可能变优,因为最后一个负值的下标一定是不变或者向前移动
所以,
我们直接对所有未固定的位置的数按从大到小排序再按顺序填回去即可。
C. Mortal Kombat Tower
题目大意:
你和你的朋友在玩一款游戏,现在有 n n n个boss依次排在你们的面前。你和你的朋友轮流打怪(你的朋友先打),每人每次可以打1到2个怪(不能不打)。怪分为普通怪和精英怪,你的朋友比较弱,每次打精英怪的时候需要使用必杀技。而你比较强,精英怪和普通怪都乱杀。现在希望你朋友用必杀技的次数越少越好,问你的朋友最少需要使用多少次必杀技。 a i = 0 a_i=0 ai=0代表普通怪, a i = 1 a_i=1 ai=1代表精英怪。
数据范围: 1 ≤ n ≤ 2 ⋅ 1 0 5 1≤n≤2⋅10^5 1≤n≤2⋅105
题解:
动态规划,首先设定状态
d p ( i , 0 ) dp(i,0) dp(i,0)表示前 i i i个怪打完了,最后一个怪是你打的,这时最少使用的必杀技次数
d p ( i , 1 ) dp(i,1) dp(i,1)表示前 i i i个怪打完了,最后一个怪是你朋友打的,这时最少使用的必杀技次数
转移方程
每次转移只用考虑是打一个更优还是打两个更优即可
d p ( i , 0 ) = m i n ( d p ( i − 1 , 1 ) , d p ( i − 2 , 1 ) ) dp(i,0)=min(dp(i-1,1),dp(i-2,1)) dp(i,0)=min(dp(i−1,1),dp(i−2,1))
d p ( i , 1 ) = m i n ( d p ( i − 1 , 0 ) + a [ i ] , d p ( i − 2 , 0 ) + a [ i ] + a [ i − 1 ] ) dp(i,1)=min(dp(i-1,0)+a[i],dp(i-2,0)+a[i]+a[i-1]) dp(i,1)=min(dp(i−1,0)+a[i],dp(i−2,0)+a[i]+a[i−1])
注意一下边界。
D. Trash Problem
题目大意:
有 n n n堆石子,每堆石子的位置在 p i p_i pi。每次操作可以选择某一堆石子,将其向左或向右移动一格。当两堆石子移动到同一个位置的时候他们就会合并成一堆,问最少需要移动多少次,使得最终只有最多两堆石子。
之后有q组询问,每次会在 x i x_i xi的位置新增一堆石子或者将 x i x_i xi位置 的石子去除,同样问上述的问题。
保证新增石子的时候该位置没有石子,去除石子的时候该位置一定有石子,并且前面的操作会对后面的产生影响,即不是操作完还原。
数据范围: 1 ≤ n , q ≤ 1 0 5 , 1 ≤ p i , x i ≤ 1 0 9 1≤n,q≤10^5,1≤p_i,x_i≤10^9 1≤n,q≤105,1≤pi,xi≤109
题解:
由于最后可以合并成至多两堆石子,所以并不需要把所有石子合并到一起。
那么可以想象答案是 m a x ( p i ) − m i n ( p i ) − m a x ( p i − p i − 1 ) max(p_i)-min(p_i)-max(p_i-p_{i-1}) max(pi)−min(pi)−max(pi−pi−1),即找到一段最大的间隔,左边的石子合并成一堆,右边的石子合并成一堆。
我们用一个 s e t set set维护石子的位置,一个 m u l t i s e t multiset multiset维护间隔。
每次加入一堆石子, s e t set set中加入这堆石子并维护 m u l t i s e t multiset multiset中的间隔
auto it = pos.find(x);
找pos中前面石子堆位置的和后面的石子堆位置的时候,可以使用 p r e v ( i t ) prev(it) prev(it)和 n e x t ( i t ) next(it) next(it)。如果编译器不支持,可以写成:
int Prev(auto it, int k=1){while (k--) it--; return *it;}
int Next(auto it, int k=1){while (k--) it++; return *it;}
这个是找前面第 k k k个和后面第 k k k个( k k k比较小的时候)
E. Expected Damage
题目大意:
有 n n n个怪物,每个怪物的攻击力为 d i d_i di。现在你有 a a a层盾,每层盾的防御力为 b b b。
当怪物攻击时
如果没有盾,你会掉 b b b的血;
如果有盾但 d > = b d>=b d>=b,你会被打掉一层盾;
如果有盾但 d < b d<b d<b,则没有影响;
问怪物随机排列来攻击你,你掉血量的期望;有多次询问,每次给出不同的 a a a和 b b b。
数据范围: 1 ≤ n , m ≤ 2 ⋅ 1 0 5 , 1 ≤ d i , b ≤ 1 0 9 , 1 ≤ a i ≤ n 1≤n,m≤2⋅10^5,1≤d_i,b≤10^9,1≤a_i≤n 1≤n,m≤2⋅105,1≤di,b≤109,1≤ai≤n
题解:
题目的是要求总共伤害的期望,我们可以计算每个怪物对我们造成伤害量的期望,再求和。
考虑每个怪物什么时候可以造成伤害,造成伤害的条件为盾打完了。
那么每个怪能打到人的情况就是在他之前有至少 a a a个怪的攻击力大于或等于 b b b
对于每个输入的 a a a和 b b b,二分找到攻击力比 b b b小的部分和比 b b b大的部分
对于小的部分,我们需要前面至少有 a a a个攻击力比 b b b大的,假设总共有 k ( k > = a ) k(k>=a) k(k>=a)个攻击力比 b b b大的,那么,
概率为 k + 1 − a k + 1 \frac{k+1-a}{k+1} k+1k+1−a,因为我们考虑攻击力比 b b b大的以及当前这个的全排列,当前这个必须排在后 k + 1 − a k+1-a k+1−a个才能造成伤害。
同理对于大的部分,其造成伤害的概率为 k − a k \frac{k-a}{k} kk−a,最后加权求和即可。
F. Equal Product
题目大意:
1 ≤ x 1 < x 2 ≤ n 1 \leq x_1 < x_2 \leq n 1≤x1<x2≤n
1 ≤ y 2 < y 1 ≤ m 1 \leq y_2 < y_1 \leq m 1≤y2<y1≤m
x 1 ⋅ y 1 = x 2 ⋅ y 2 x_1 \cdot y_1 = x_2 \cdot y_2 x1⋅y1=x2⋅y2
l ≤ x 1 ⋅ y 1 ≤ r l \leq x_1 \cdot y_1 \leq r l≤x1⋅y1≤r
输入 n , m , l , r n,m,l,r n,m,l,r,对于每个 x 1 x_1 x1( 1 ≤ x 1 ≤ n 1 \leq x_1 \leq n 1≤x1≤n)输出一组满足上述条件的整数解,或者无解输出 − 1 -1 −1
数据范围: 1 ≤ n , m ≤ 2 ⋅ 1 0 5 , 1 ≤ l ≤ r ≤ n m 1≤n,m≤2⋅10^5,1≤l≤r≤nm 1≤n,m≤2⋅105,1≤l≤r≤nm
题解:
因为 x 1 ⋅ y 1 = x 2 ⋅ y 2 x_1 \cdot y_1 = x_2 \cdot y_2 x1⋅y1=x2⋅y2,所以我们设
x 2 = x 1 ⋅ b a x_2=x_1 \cdot \frac{b}{a} x2=x1⋅ab,那么有 y 2 = y 1 ⋅ a b y_2=y_1\cdot \frac{a}{b} y2=y1⋅ba
我们预处理出所有的 x 1 x_1 x1和他的因子 a a a,枚举每个点对 ( x 1 , a ) (x_1,a) (x1,a)
对于每个点 ( x 1 , a ) (x_1,a) (x1,a), y 1 y_1 y1的限制为 ⌈ l x 1 ⌉ ≤ y 1 ≤ m i n ( m , ⌊ r x 1 ⌋ ) \lceil \frac{l}{x_1} \rceil \leq y_1 \leq min(m, \lfloor\frac{r}{x_1} \rfloor) ⌈x1l⌉≤y1≤min(m,⌊x1r⌋)
接着我们确定b的限制
因为 x 1 < x 2 x_1 < x_2 x1<x2 ,所以 b > a b>a b>a
因为 x 2 ≤ n x_2 \leq n x2≤n,所以 b ≤ n ⋅ a x 1 b \leq \frac{n \cdot a}{x_1} b≤x1n⋅a
同时我们要求 b b b是 y 1 y_1 y1的因子
我们从小到大枚举 x 1 x_1 x1,固定 x 1 x_1 x1后从小到大枚举 a a a
x 1 x_1 x1确定后,我们可以确定 y 1 y_1 y1的取值范围,是一个区间,并且区间随着 x 1 x_1 x1的增大会在数轴上向左移动
我们维护这个区间中所有数的因子,每次枚举 a a a的时候,我们可以确定 b b b的一个区间。
现在我们需要快速知道 y 1 y_1 y1对应的区间中是否有一个数是 b b b对应的区间中一个数的倍数
我使用的是线段树维护,线段树的叶子节点为因子,维护该因子在 y 1 y_1 y1区间中对应的哪个值,即哪个值是含有该因子的。我们可以只记录最小的那个值,因为区间是向左移动的。查询时就 b b b对应的因子区间是否含有合法的 y 1 y_1 y1。
G. Three Occurrences
题目大意:
给定一个长度为 n n n的序列,问该序列中有多少个子串,使得其中相同的数恰好出现三次。
数据范围: 1 ≤ n ≤ 5 ⋅ 1 0 5 , 1 ≤ a i ≤ n 1≤n≤5⋅10^5,1≤a_i≤n 1≤n≤5⋅105,1≤ai≤n
题解:
cf给的题解很巧妙,复述一下。
首先考虑第一个问题:如何找到有多少子串,使得其中相同的数出现的次数是三的倍数
我们给每个数随机给他一个长度为 K K K的序列,这个序列中的只包含0,1,2。然后定义一个三进制下的无进位加法。这样的话,三个完全相同的序列相加,将得到一个全0的序列。
而只要
K
K
K比较大,以下结论的正确率就会很高。
结论:如果
m
m
m个序列相加为全0序列,那么这
m
m
m个序列对应的
m
m
m个数中,相同的数出现的次数就是三的倍数。
那么我们可以使用 m a p map map维护出某个前缀和(这里的前缀和指的是数字对应序列的前缀和)的个数,枚举右端点,在 m a p map map中找到与当前前缀和相同的前缀和有多少个。
第二个问题:如何找到有多少子串,使得其中相同的数出现的次数<=3
这个使用双指针的做法,枚举右指针的位置,维护序列中每个数的出现次数,如果出现次数 > = 4 >=4 >=4了,就移动左指针,知道该数的次数 < = 3 <=3 <=3。
综合上述两个问题,双指针维护出现次数,移动指针的时候维护 m a p map map,在 m a p map map中维护前缀和出现次数即可
建议大家去读下Solution 1 (pikmike)的代码,写的很漂亮