题目传送门
做法
在此使用贪心
贪心做法
我们把
i
i
i 位置的数设为
a
l
a_l
al。
首先,将
a
l
a_l
al 一直当作
y
y
y,把别的一个任意数当作
x
x
x 进行操作,直到
a
l
=
0
a_l = 0
al=0 止。接着将
a
l
a_l
al 当作
x
x
x,加别的非
0
0
0 任意数当作
y
y
y 进行操作,直到所有数均为
0
0
0 为止。
贪心证明
其实需要一个转化。
设
c
n
t
i
cnt_i
cnti 为将
a
i
a_i
ai 进行多少次形如
a
i
=
⌊
a
i
3
⌋
a_i = \lfloor \frac{a_i}{3} \rfloor
ai=⌊3ai⌋ 的操作能使
a
i
a_i
ai 变为
0
0
0。
则原问题变为,有三种操作:
- 将 c n t x + 1 cnt_x + 1 cntx+1, c n t y − 1 cnt_y - 1 cnty−1
- 将 c n t y − 1 ( c n t x = 0 ) cnt_y - 1 (cnt_x = 0) cnty−1(cntx=0)
- 将 c n t y + 1 ( c n t x = 0 ) cnt_y + 1 (cnt_x = 0) cnty+1(cntx=0)
求把所有 c n t cnt cnt 变为 0 0 0 的最小操作数。
显然,第三种操作只会让结果更劣,所以我们不做第三种操作。
我们先假设存在一个 c n t x = 0 cnt_x = 0 cntx=0,那么由于每次总和最多 − 1 - 1 −1,所以最少要进行 c n t cnt cnt 的总和次操作,如果选择一个不为 0 0 0 的为止和 x x x 进行操作二,就可以在 c n t cnt cnt 的总和次操作下完成,因此这么做是最优的。
接下来,我们要证明,将一个 c n t cnt cnt 最下的数变为 0 0 0,再进行上面的操作是最优的。
由于操作1不会改变总和的大小,也就是说,前面怎么做对后面的操作次数都没有影响。因此答案就是让
0
0
0 存在的最小次数
+
c
n
t
+ cnt
+cnt 的总和,让
0
0
0 存在的最小次数就是
c
n
t
l
cnt_l
cntl,所以
A
n
s
=
c
n
t
l
+
∑
i
=
l
r
c
n
t
r
Ans = \displaystyle cnt_l + \sum_{i = l}^{r} cnt_r
Ans=cntl+i=l∑rcntr
=
2
×
c
n
t
l
+
∑
i
=
l
+
1
r
c
n
t
r
\displaystyle = 2 \times cnt_l + \sum_{i = l + 1}^{r} cnt_r
=2×cntl+i=l+1∑rcntr
实现
c n t cnt cnt 数组直接暴力预处理即可,然后将其求前缀和 s s s, ∑ i = l + 1 r c n t r = s r − s l \displaystyle \sum_{i = l + 1}^{r} cnt_r = s_r - s_l i=l+1∑rcntr=sr−sl, c n t l = s l − 1 cnt_l = s_l - 1 cntl=sl−1。
代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 200010;
int s[N];
int main()
{
int T;
scanf("%d", &T);
for (int i = 1; i < N; i ++ )
{
int x = i;
int cnt = 0;
while (x)
{
x /= 3;
cnt ++ ;
}
s[i] = s[i - 1] + cnt;
}
while (T -- )
{
int l, r;
scanf("%d%d", &l, &r);
int cnt = s[l] - s[l - 1];
printf("%d\n", cnt * 2 + s[r] - s[l]);
}
return 0;
}