從七點半做到五點,我裂開了。
主要原因是 T2 暴露出我完全沒有代碼能力、 T3 暴露出我不會小學數學。
T2 的 sb 錯誤包括但不限於
- 定義的變量忘記預處理。
- MLE 。
- 樹上單調棧寫掛。
T3 的 sb 錯誤包括但不限於
- 20*20=4000 。
- 寫了
cnt+=2
之后誤以為自己寫的是cnt++
,於是誤以為自己要大約 5000 次操作。
T1
從 \(s_i\) 向 \(t_i\) 連一條邊,得到一個 3 個點、若干條邊的圖。
一次操作就是交換兩條邊的起點。
隨便貪心即可。正確性看着就很沒問題。
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\) 次黑箱就做完了。