IOI2021 Day2 简要题解

从七点半做到五点,我裂开了。

主要原因是 T2 暴露出我完全没有代码能力、 T3 暴露出我不会小学数学。

T2 的 sb 错误包括但不限于

  • 定义的变量忘记预处理。
  • MLE 。
  • 树上单调栈写挂。

T3 的 sb 错误包括但不限于

  • 20*20=4000 。
  • 写了 cnt+=2 之后误以为自己写的是 cnt++ ,于是误以为自己要大约 5000 次操作。

T1

\(s_i\)\(t_i\) 连一条边,得到一个 3 个点、若干条边的图。

一次操作就是交换两条边的起点。

随便贪心即可。正确性看着就很没问题。

https://loj.ac/s/1167042

T2

无脑的想法就是把每一种基环树都预处理出来,然后询问的时候暴力跳,直到超过下一个人。但是这种想法没有前途。

尝试分析跳跃的性质,发现没有性质。

观察收益。输了的时候收益可能很小,但是赢的收益是其能力值,看起来会比较大。

如果有一个人我现在打不过,但是过了若干次之后把他打败了,那么我的能力值此时至少乘了 2 。

但是下一个现在打不过而以后打得过的人也不好找。

但是我们主要的目标是让自己的能力值乘 2 ,所以可以把 \((2^i,2^{i+1}]\) 能力值的人分成一组,每次只要找到一个打得过的组别够大的人即可。

这样只有 \(\log w\) 个基环树。乱搞搞可以做到 \(O((n+q)\log n\log w)\) 。然而写完就会发现跑不过去。

考虑把乘 2 变成乘 \(k\) ,那么复杂度就是 \(O(n\log_k w\log n+qk\log_k w\log n)\) 。取 \(k=8\) 即可获得 \(1\over 3\) 的常数。

loj 不太喜欢我的代码,但是在 oj.uz 过了。

https://oj.uz/submission/438455

T3

先考虑怎么比较两个数 \(x,y\) 的大小。

\(b>k\) ,所以假装我们在模 \(2^{k+1}\) 意义下做加法。那么把 \(y\) 按位取反(下面称为 \(rev(y)\) ),加上 \(x\) ,即得到 \(x-y-1\) 。观察第 \(k\) 位是 0 还是 1 即可。

为了把最小值取出来,需要 \(2\log k\) 的次数把第 \(k\) 位扩展到所有 \(k+1\) 位,然后和 \(x\) 取 and 。

但是这样一次差不多需要 20 次操作,非常爆炸。

因为 \(b=2000>nk\) ,所以考虑一次多进行一些操作。假设 \(X,Y\) 分别是 \(n/2\) 个数连在一起,我们需要分别求 \(\min\) ,然后把 \(\min\) 放在 \(X\) 的位置。

问题 1 :数连在一起,所以“第 \(k\) 位”会是下一个数的第 0 位。

解决方案:无所谓。只需要 \(X+rev(Y)\) 的时候“第 \(k\) 位”恰好有一个 0 一个 1 ,就不会有什么问题。给 \(X+rev(Y)\) 异或上 \(X\oplus Y\) 即可。

问题 2 :下面的数加在一起的时候可能会给上面的数进位。

解决方案:同样无所谓。\(x-y-1\)\(x-y\) 几乎没有区别。

这样我们就可以用大约 20 次操作把 \(n\) 个数折半。所以第一问就用 \(20\log n\) 次操作做完了。

我们现在有一个大约 20 次操作可以比较 199 个数的神奇黑箱,考虑找一个和这比较契合的排序方法。

思来想去发现没啥好用的,那就自己编。

// 我称之为 冒泡排序-plus
rep(k,1,(n+1)/2)
{
	for (int i=1;i<n;i+=2) if (a[i]>a[i+1]) swap(a[i],a[i+1]);
	for (int i=2;i<n;i+=2) if (a[i]>a[i+1]) swap(a[i],a[i+1]);
}

不太清楚为什么这个能够完成排序任务,但它确实完成了。

那么就只需要调用 \(n\) 次黑箱就做完了。

https://loj.ac/s/1167378

posted @ 2021-06-28 17:36  p_b_p_b  阅读(1060)  评论(0编辑  收藏  举报