Codeforces Round 930 (Div. 2)
Codeforces Round 930 (Div. 2)
A. Shuffle Party
题意
给出一个数组$a_1,a_2,…,a_n$ 起初$a_i = i$ ,之后定义$swap(k)$ 操作为 : 找到最大的,不等于$k$的因子$d$ ,交换$a_d$和 $a_k$ 。
下面令$i = 2,3,…,n$ ,依次进行$swap(i)$ ,问最终数值$1$会在哪个位置。
思路
数值1会随着不断运行swap来移动位置,我们可以观察1的移动轨迹。
于是我们模拟一下操作会发现, i = 2时, $swap(2)=swap(a_1,a_2)$ 此时1移动到了位置2 。接下来若想要让位置2发生交换,那么就必须找到一个最小的k,使得这个k所对应的d恰好是2 。 并且k尽可能地小。 于是我们想到了2的2倍4 。 下一步数值1便从位置2 移动到了位置4。接下来就是4的2倍即8, 数值1又从位置4移动到了位置8 。 这样循环往复,直到数值1移动到了$2^j$ , 并且满足$2^j \le n < 2^{j+1}$ 。数值1便无法移动,此时就是他的最终位置。
代码
找到小于n的最大的2的次幂即可。
1 |
|
B. Binary Path
题意
有一个$2n$的 $01$地图,一个蚂蚱从$(1,1)$开始走,终点位于$(2,n)$ 。每次可以*向右或者向下走 ,蚂蚱走到终点后,会形成一个路径,问这个路径的字典序最小情况是什么, 以及有多少种走法可以出现这种最小字典序的路径。
思路
刚开始看错题了呜呜呜
为了使字典序最小,我们可以总结出如下几个策略:
如果右边的数和下边的数 , 一个是0,而另一个是1,那么走0那个方向一定字典序更小。
如果右边的数和下边的数相等, 那么走右边方向,因为一旦向下走到第二行走便不能回第一行,而在第一行则可以随时转去第二行。
如果很难理解策略2,可以尝试理解一下这个例子:
000111
011111
当位于起点时,右边和下边都是0 ,但此时若向下走,则之后只能向右走而导致最终路径为0011111
,若向右走则是0001111
。
于是我们便得出了最优解的路线,再考虑这种方案数:
我们可以发现只有当已经走到了第n列,或者下方为0并且右方为1时, 会选择向下走。那么我们假设在$i = p$ 时选择了向下走。
那么多种方案的出现仅有可能是由于可以提前选择向下走(在$p-1,p-2,…$时选择了向下走),并且路径完全相同。
于是每当 $a[0][j] == a[1][j-1]$ 便可以选择提前一格向下走,从而增多一种方案。于是总的方案数变为了: 对于$j = k,k+1,…,p-2,p-1$ , 都有$a[0][j] == a[1][j-1]$,而$k$尽可能小来使得$j$的可取数量尽可能多 。即在p之前的一些连续格子中,有多少个格子满足 右边和下边的数相等 。
代码
在实现代码时,有一些可能会有帮助的小技巧。
我们使用$flag$作为当前所在的行,flag = 0表示当前位于第一行,flag = 1表示当前位于第二行。循环枚举$i$从$1$到$n$表示列,于是求路径时便可以用 $s += a[flag][i]$ 来得到。
我们可以通过比较 $a[0][i+1]$和$a[1][i]$ 的大小关系来进行操作 。
如果$a[0][i+1]>a[1][i]$ 则表示需要向下走,于是便可以设置flag = 1。 但我们发现当i = n时不论如何都要向下走,于是我们不妨提前将$a[0][n+1]$设置为2,表示无论$a[1][n]$ 为何值,此时$a[0][n+1]>a[1][n]$ 都会成立,便选择了向下走。
而向下走时,i并不会变化,但for循环会使得i每次都加1,于是我们可以使用
i--
来抵消每次for循环产生的i++
我们用$cnt$表示已经出现几个
右等于下
的格子了,如果$a[0][i+1] = a[1][i]$ ,那么就让cnt++
如果$a[0][i+1] < a[1][i] $ ,那么我们就需要重置cnt为0 。
下面是完整核心代码(只有solve()部分,其他部分和A题相同 ,所以如果不知道rep是什么可以看A题中的define声明)
1 | void solve(){ |
C. Bitwise Operation Wizard
题意
这是一个交互题,有一个长度为n的数组p$(p_1,p_2,…,p_n)$,他的n个元素分别为$(0,1,2,…,n-1)$ 排列顺序未知。每次询问你可以提供四个下标$(a,b,c,d)$ 裁判会回应你$p_a | p_b$和 $p_c | p_d$ 之间的大小关系(即$<.=.>$ ) 现在需要你在询问至多3*n次后, 得到一对下标$i,j$ ,满足 $p_i \oplus p_j$ 最大化。 ($\oplus$ 表示按位异或)
思路
我们考虑$p_i \oplus p_j$ 最大为多少。 较容易得知 ,若n-1的二进制表示有k位。 那么最大值就是这k位全部为1。 即整个数组中的最大数mx ,与另一个数$num$的异或。 接下来我们分别找这两个数。
为了找$num$, 我们发现这个数就是在这$k$位中,对$mx$的各位取反得到的数。 (如若$mx = 10100{(2)}$ 那么$num = 01011{(2)}$)
先找到最大值,我们发现可以令$a = b , c = d$ 这样裁判就会明确指出某两个数的大小关系。 于是我们可以这样子枚举整个数组,进行挨个儿比较。就可以用$n-1$ 次确定 最大数 所处的位置。
1 | for(int i = 1;i<=n-1;i++){ |
之后我们找所有的满足$p_i | mx$ 为最大(即k位全是1) 的数的集合。 他们一定满足了对于$mx$上数位为$0$的位置,$num$的那个位置一定为$1$ 。
我们使用vec来记录数据,每当遇到更大的$p_i | mx$ 就清空vec并放入$p_i$ ,如果遇到了相等的数,就继续向vec中装入。
这样就可以在n次询问下得到所有的满足对于mx上数位为0的位置,num的那个位置一定为1
的数,那么我们为了发现,在$mx$中数位为1的位置上,num必须是0才能保证二者异或得到1。 也就是说要在vec中找到0最多的那个数,也就是最小的那个数。
于是我们可以在$vec.size() - 1$次询问下,得到vec中最小的数mn。
于是num就是mn,我们的最终答案就是 mn和mx。
代码
1 |
|