NOIP 2024 遊記 & 賽前訓練總結
前面都是比賽前的訓練,會含有一些比賽經驗。遊記寫在最後。
day #-18(11.11)
賽時
今天做信友錯的模擬賽。
第一題是和最短路有關的,看到 \(n\le 500\) 就想到了 \(n^3\log n\),然而看了很久都不會做,於是果斷火速打了 \(O(n^4)\) 的暴力走人,get 50pts。
然後看第二題,發現是最大異或路徑,正好最近剛學了線性基,於是想到之前做的一道線性基的題目,把每個環加入線性基。然後就能做到 \(n^2\),再補一個樹的性質。get 55pts。此時比賽剛過一小時。
很快啊,其他人還在做 T1,我就開了 T3。看了一會想到用並查集維護,再記錄修改前的以實現撤銷。發現大樣例都過了,此時我還以為複雜度是正確的,此時比賽剛剛過半。但是去了一下洗手間,就想到可以構造一條鏈卡到平方,這個做法只能過隨機的性質和不撤銷的性質。於是又補了鏈的性質。get 55pts。
此時光靠暴力已經得到 160pts 了。
比賽剛過半,看了一下第四題,覺得還是要先補一下第一題的性質,補了一個性質 A,多得了 10pts。
這時我去嘗試想中間兩道題的正解,無獲,於是開始打最後一題的暴力。
後半程有點鬆弛。
發現暴力分很多啊,在最後 20 分鐘寫完了 \(n\le 9\) 的 40pts 暴力,然後又補了前兩個性質共 8pts。
賽後 & 總結
然後——比賽結束,最後一題 48pts \(\to\) 60pts,最後總分 \(50+55+55+60=230\) 喜提總榜 14,jz 第 3,原來是暴力場。
賽後發現 T1 是線段樹分治的思想,還有 Floyd 插入單個點的技巧,我們只以一個點集內的點為中轉點轉移(插入順序沒有要求),就可以求出只經過(不包括起點終點)這個點集內的點的最短路。
T2 是線性基的結論,\(qmax(i\oplus j)=qmax(i)\oplus qmin(j)\)。
T3 是樹剖+線段樹的 4K 大資料結構,就是把輕鏈的貢獻全部掛在重鏈上,有點類似動態 DP 那種。
T4 是論文題,用結論的式子計數。而暴力就是遞迴,然後在遞迴過程中進行剪枝就能跑得比較快。
總結:這場比賽時間分配還不錯,前面暴力打得比較快,除錯也沒花多少時間,大概是經驗越來越豐富了吧。但可惜後面充裕的時間內沒有想出正解,其實正解是不難的,還有提升空間。
改題經驗
改 T3 的時候有一個陣列沒開 long long
,幸好很快就查出來了。
改 T1 的時候在分治時把列舉 \([l,mid]\) 寫成 \([1,mid]\) 了。
day #-14(11.15)
今天輸了!
20:35 開始打 CF div.2。
順序開題。
T1 是唐題,T2 是唐題。
T3 我蠢了,1 h 時還沒過,於是去寫 T4。
T4 也想了不少時間,終於想到一個用 set+BIT 的做法。
T5 是樹同構,我還以為要換根於是沒寫。
T6 是互動,沒看。
最後成果:T1+T2+T4。
賽後發現 T3 的正解就是我想的那樣,結果 tm 的 T5 的同構是有根樹下的,又被騙了。
譴責 CF 的題面跟 shi 一樣長。
總結:
- 要有耐心,不要因為前面的題不會,後面就不去想了。
- 不要花太多時間在一道題目上,扔掉後就不要再去想它了。
day #-13(11.16)
今天又做心有錯。
T1 簡單題。
後面都不會做,把暴力都拿了。
預計:100+60+60+50=260。
實際:72+60+60+20=212。
T1 我自信地認為 \(n^3\times V\) 不會爆 long long
,導致我沒有取模。
而 T4 不知道為什麼 \(O(n^3)\) 沒有拿到第二檔分,tm 的 OI 賽制還綁 subtask。
賽後
T2 是對於長度相同的區間的線段樹是同構的,運用這個性質記憶化遞迴。
T3 題面很簡單,正解是神奇雜湊,被騙啦,哈哈哈,把 shi 題放在 T3 讓人以為是科技題。大概是把 \(n\) 個位置用雜湊值轉化成 \(\sum\) 個字元的不同情況。
T4 是分治,矩形分為跨過分界線和不跨過兩種,不跨過是子問題,遞迴求解;跨過可以暴力做。每次對當前矩形割長的一邊,這樣複雜度就正確了,時間是 \(T(n)=4T(n/2)+O(n^2)=n^2\log n\)。
總結
- 要有耐心,要充分利用時間。
- 要注意資料範圍,記得取模,不要想當然。
- 神奇的題要想想神奇的演算法,比如雜湊之類的,這又讓我想起星戰那道奇葩題。
dsy #-9(11.20)
心有錯。
賽時
T1 想到二分答案,然後就不會做了,我都要以為是罰坐場了。
前面浪費了一個小時,然後去開 T2,寫了一個 \(O(n^2)\) 暴力,對著邊權討論一下,然後終於想到了用線段樹模擬最短路的過程,寫完已經十一點了。
後面的時間打了剩下三題的暴力,這時感覺 T4 有點像雜湊,但是覺得敲不出來了,就沒繼續想。
賽後
發現 T1 是我不會證的結論(猜結論題),T3 是 SG 函式的規律(打表題),T4 真的是雜湊。
之後新學了博弈論相關的知識。
總結
- 這場比賽在第一題浪費太多時間了,應該猜到題目不按難度排列的,一定要先做擅長的題和有想法的題。
day #-4(11.25)
今天的題都挺好的,但就是我沒做出來幾道,感覺思維不夠敏捷了?這一週一定要努力了。
今天還是做心有錯,T1 浪費了我一個半小時,T2 已經接近正解了,T3 用了錯解結果過了,T4 其實不算難題但我沒看。
T1
TAG:線性篩、質因數分解、最最佳化分解。
這道題很容易想到 \(O(T\dfrac{\sqrt[k]n}{\ln\sqrt[k]n})\) 的做法,就是當 \(k=3\) 時,質數分解到 \(2^{20}\),之後列舉所有質數即可。
然後我就絞盡腦汁沒想到正解,但我想到和正解很接近的思路:
發現有很多小的質數對答案沒有貢獻,那怎麼去掉它們呢?
然後我並不知道怎麼去掉它們,原來只需要把 \(\sqrt[k+1]n\) 的質數篩掉,剩下的最多是三個質數相乘,這個時候直接把剩下的 \(n\) 開根即可,時間複雜度 \(O(T\sqrt[k+1]n)\),由於 \(k\) 隨機所以可過。
細節:我們需要用 powl(n,(long double)1/K)
進行開根,但由於指數是小數,所以還是有精度問題,我們還需要 roundl
進行四捨五入。
T2
TAG:逆序對、二維平面、掃描線。
這題我比賽時想到了:
如果選 \(i,j(i<j)\),那麼答案為 \(1+2\sum_{k=i+1}^j [a_i>a_k>a_j]\)。
賽時寫了一個嚴格 \(O(n^2)\) 的做法,就是預處理 \(f_{i,j}\) 表示前 \(i\) 個數中 \(a_i\) 大於 \(j\) 的個數。
賽時還想到放到二維平面上,把每個位置看成一個點 \((i,a_i)\),那麼就是求一個矩形內點最多有多少個,要滿足矩形左上角和右下角是一個點。
賽時覺得左上角應該是一個上凸包,右下角是一個下凸包,然後時間不夠了,也沒有再深度思考。
而實際上左上角是字首最大值,右下角是字尾最小值。
發現我們列舉點對 \((i,j)\) 不好統計,而且點對也是 \(O(n^2)\) 的。
考慮 \((k,a_k)\) 對那些 \((i,j)\) 有貢獻。
發現滿足條件的 \(i\) 是一個區間 \(j\) 也是一個區間,記它們為 \([l_{1,k},r_{1,k}],[l_{2,k},r_{2,k}]\)。
那麼再把它放到二維平面上,就是 \(x=[l_{1,k},r_{1,k}],y=[l_{2,k},r_{2,k}]\) 的一個矩形內的點有 \(+1\) 的貢獻。
那麼線段樹掃描線找到被最多矩形覆蓋的點即可。
把一個矩形 \([l_{1,k},r_{1,k}],[l_{2,k},r_{2,k}]\),分為 \((l_{1,k},r_{1,k},l_{2,k},1)\) 和 \((l_{1,k},r_{1,k},r_{2,k}+1,-1)\) 即可。
T3
TAG:最小生成樹、Prim、Kruskal、線段樹、均攤、啟發式合併。
做法 1
考慮 Kruskal。
把點按點權重標號,然後用 vector
和並查集維護每一個連通塊內的點與每個點屬於哪一個連通塊,set
維護當前每個不同的連通塊編號。
對於給的邊就直接加;對於剩下的邊,我們每次嘗試向一個不同連通塊內的點連邊,失敗次數是 \(O(m)\) 的,成功次數是 \(O(n)\) 的,於是均攤複雜度就是正確的。
合併 vector
需要啟發式合併。
時間複雜度 \(O((n+m)\log n)\)。
考場上想到了均攤,但是沒想到如何維護不同的連通塊,但是寫了一個相似的可以被卡掉的做法,但結果過了。
做法2
考慮 Prim,也就是每次找離連通塊內最近的點加入連通塊。
可以用線段樹維護離連通塊的距離與取出一個最近的點。
更新距離時,所有點的更新被給定的邊分成了 \(O(m+n)\) 個區間賦 \(a_i\)。
時間複雜度 \(O((n+m)\log n)\)。
T4
發現取帶權重心最優。
這題是動態尋找帶權重心,用倍增找。然後權值和需要用較複雜的容斥,需要預處理多個樹上 DP 陣列,同時還需要換根 DP。
用到的演算法並不難,一些結論也大概能猜到,就是在計算上的細節比較多。
總結
- 題目難度不一定順序,不要花太多時間在看起來很簡單的題上。
- 正難則反,多從不同角度思考問題。
- 多從學過的演算法中找思路和靈感。
- 使用
powl
時,記得用ruondl
,記得用long double
。 - 可以嘗試把序列上的統計問題放到二維平面上思考。
- 統計 \(x\) 有多少個 \(y\) 時,可以考慮對於每個 \(y\) 思考它能貢獻到哪些 \(x\)。
- 可以用線段樹掃描線做最多覆蓋問題,用差分做矩形的掃描線。
- 用啟發式合併合併
set
,vector
等。 - 最短路、最近點可以考慮放到線段樹上最佳化更新和取點。
- 樹上問題多考慮考慮重心或直徑,從中尋找性質。
day #-3(11.26)
P1445 [Violet] 櫻花
下午做了這一道奇葩的藍題,我想了一個小時都不會做。
題意大概是給定 \(n,1\le n\le 10^6\),問 \(1/x+1/y=1/(n!)\) 的正整數解的個數。
大概是這樣的:
於是可以得到 \(y>n!\),那麼設 \(y=n!+k\),\(k\) 是正整數。
帶入得
於是對於任意 \(k\) 滿足 \(k\mid n!^2\),都有對應的一個 \(x\),也就有對應的一組 \((x,y)\)。
於是答案就是 \(d(n!^2)\),考慮列舉每個質數 \(p\) 的倍數,計算 \(p\) 在每個數中的指數和,記為 \(cnt_p\),答案就是 \(\prod_p(cnt_p+1)\)。
時間複雜度為 \(O(n\ln n)\)。
day #-2(11.27)
今天掛了 90 pts!!!
原因是 T1 在本地過了大樣例後沒造大資料,也沒寫對拍,於是最終的程式碼陣列越界,然而測大樣例時陣列越界在 windows 下還能正常跑!
以後一定要造大資料或者在虛擬機器上跑,windows 不可信。
今天做信友隊。
T1
TAG:容斥、列舉。
T1 考慮如果兩個點集有交,那麼交的部分就減去,列舉一個點,再列舉兩個有交點集所在的位置,給它們打上一個減去的標記,這部分是 \(O(nmk^2)\)。
然後列舉兩個點集計算答案,這部分是 \(O(n^2m^2)\)。
於是時間和空間都是 \(10^8\) 級別。
T2
TAG:隨機化、調整法。
sb 出題人把 sb 腦電波題放在 T2,全場沒人過。
題目大意:對於一個 \(n*n\) 的方陣,每個格子是 \(0/1\),方案數定義為不經過 \(0\) 每次往下或往右,從左上角走到右下角的方案數。求構造一個方案數為 \(L\) 的 \(n\le 30\) 的方陣。
這道題是隨機化,然而 sb 題解並沒有證明,程式碼也很好打,就是調整法,每次貪心地調整最大的,多隨機幾次初始矩陣就可以透過。
T3
TAG:揹包、增量構造、多項式、遞推。
這題是好題。
題目大意:給你 \(n\) 行 \(m\) 列數字,每行選一個數,求在所有選擇方案中選出的數的中位數的期望。
賽時做法
考慮列舉最後的中位數 \(x\),然後計算中位數為 \(x\) 的方案數,考慮揹包設 \(f_{i,j,k}\) 表示前 \(i\) 行選了 \(j\) 個小於 \(x\) 和 \(k\) 個等於 \(x\) 的方案數。
由於 \(k\) 上界的和為 \(O(nm)\) 於是複雜度為 \(O(n^3m)\)。
我真的不懂為什麼 \(O(n^4)\) 和 \(O(n^5)\) 都一樣只有 20pts,但願正式賽會好一點。
然後我就想怎麼最佳化,發現這樣求方案數的話,不同中位數之間是沒有關聯的。
考慮求中位數小於等於 \(x\) 的方案數 \(ans_x\),於是等於的就是 \(ans_x-ans_{x-1}\)。
中位數小於等於 \(x\) 的方案數就要求至少有 \(\dfrac {n+1} 2\) 行選的數要小於等於 \(x\),發現這隻與每一行小於等於 \(x\) 的數的個數有關。
然後我就不會了,覺得這個東西還是需要揹包。
正解
我們考慮可以把揹包刻畫成多項式的形式,如果我們要求中位數小於等於 \(x\) 的,設 \(cnt_i\)為第 \(i\) 行小於等於 \(x\) 的個數,那麼就有 \(ans_x=[x^{\frac{n+1} 2}]F(x) =[x^{\frac{n+1}2}]\prod_{i=1}^n [(m-cnt_i)+cnt_ix]\)。
考慮把所有 \(a\) 排序後依次加入 \(cnt\) 中,這樣就能算出每個 \(x\) 的 \(ans_x\)。
考慮加入第 \(i\) 行的 \(a\) 後多項式怎麼變化,考慮到有 \(O(nm)\) 個數,我們只需 \(O(n)\) 暴力修改多項式即可。
初始時 \(F(x)=m^n\)。
然後 \(cnt_i\to cnt_i+1\),我們可以先除以再乘上。
先考慮乘上,如果我們乘上 \((a+bx)\),那麼設 \(f_i\) 為 \(i\) 次項係數,則
移項後就能得到除法的式子,如果我們除以 \((a+bx)\),則
發現乘法要從大往小做,除法要從小往大做。
T4
TAG:期望、DP、高斯消元。
這道題鑑定為沒有使用過高斯消元做期望問題,現在會了。
part 1
先判斷無解的,對 \(O(n^2)\) 個點建圖,從能出去的點跑搜尋看哪些點出不去。
part 2
對所有 \((i,v),|v|\le n\) 的點取出來,建邊然後跑高斯消元,時間為 \(O(n^6)\)。
part 3
發現如果 \(v>O(\sqrt n)\) 那麼前面已經走了 \(O(n)\) 距離,已經走出去了,於是只需要 \(|v|\le O(\sqrt n)\) 的點。
時間複雜度為 \(O((n\sqrt n)^3)=O(n^{4.5})\)。
part 4
再最佳化點數,考慮把一個點的期望表示成 \(f_i=C+\sum_j a_{i,j} f_j\),\(C\) 是常數。如果可以那麼就能做到 \(O(n^3)\)。
考慮計算從 \((i,0)\) 開始,對於每一個 \(j\) 中間不經過其他 \((x,0)\) 到達 \((j,0)\) 的期望步數、機率,以及直接走出去的期望步數、機率。
這裡的期望步數求的是已經乘過機率的,比方說設 \(q_i\) 表示起點走到它的機率,\(c_i\) 表示起點走到它的期望步數(沒有乘上 \(q_i\) 的)、我們求的實際上是 \(q_i,C_i(C_i=q_ic_i)\),因為 \(c_i\) 是不好轉移的。
而且我們最後算的也是乘上機率後的期望步數和,即 \(C_i\) 的和。
舉例:
\(1\to^{\frac 1 2}\to 2,1\to^{\frac 1 2}\to 3\)。
就有:
\(c_2=c_3=1,q_2=q_3=\frac 1 2,C_2=C_3=\frac 1 2\)。
而就有轉移,設 \(p_i\) 為走過出邊的機率:
發現對於 \(j<i\),一定始終有 \(v<0\),對於 \(j>i\) 一定始終有 \(v>0\),即轉移的圖是一個 DAG。
於是便可以 DP,這一部分是 \(O(n^3)\)。
最後就表示成了,設 \(C'\) 表示直接走出去的期望步數,\(f_i=\sum_j q_{i,j} f_j+C'+\sum_j C_{i,j}\)。
移項得 \(-f_i+\sum _j q_{i,j}f_j=-C'-\sum_jC_{i,j}\),高斯消元 \(O(n^3)\)。
高斯消元的時候直接把無解的行列全變成 0 即可。
總結
- 一定要寫對拍、造大資料、在虛擬機器上過編譯。
- 揹包的轉移可以嘗試刻畫成多項式的形式。
- 期望問題可以高斯消元,高斯消元可以嘗試最佳化點數,可以用 DP 先處理出特殊點之間的關係以嘗試最佳化點數。
day #-1
今天做線下模擬賽,同樣的,去實際考場斷網做比賽。
今天打的非常好,沒有掛分,\(100+100+80+30=310\),獲得了前 \(10\) 名。
賽時
今天是順序開題,先看 T1,想到在上一次的生成樹上改,似乎是經典結論:在樹上加一條邊後會形成一個環,把環上最大的邊取走就可以得到新的生成樹,我寫的是 \(O(n^2\log n+qn)\) 但實際上可以用 \(Prim\) 做到 \(O(n^2+nq)\),評測時也是 800ms 驚險地卡過了。
發現有的人 \(O(n^2\log n+qn\log n)\) 被卡了、還有人 \(4*5000^2\) MLE 了。
T2 很 sb 但還是驚險地過了。發現資料隨機,想到是神奇的做法。
一開始想到類似某次 ABC 的 G 題那樣,查詢列舉子集、修改再列舉子集,資料隨機可以做到 \(O(n+3^m)\),但沒有最佳化前景。
然後又打表發現後面的詢問的答案都非常小,於是考慮每次從小到大列舉修改的位數,然後發現它跑得飛快,大約 500ms。
其實這個做法和 bfs 搜尋是本質相同的,複雜度或許可以證明是正確的,大概就是因為本質是 \(m\) 維空間的最小曼哈頓距離,空間中的點一定會越來越密集,所以跟資料隨機並沒有什麼關係。
打完前題大概是 \(9:30\),比賽是 \(7:30\) 開始的,經過 2h 了,速度中規中矩。
T3 經過我艱辛的打表,終於發現了 \(A\) 性質的充要條件,我也沒有證明,但它對了。
獲得 80 pts 後其實還剩一個多小時,但我想先去看 T4。
T4 想到網路流,但是並沒有這一檔分,想著多騙一點,結果打完了發現建模錯了,或許根本不能網路流。
只剩半小時了,趕快去寫指數級別的暴力,寫完後還剩十多分鐘了。
後面就是放到虛擬機器上過編譯了一下。
其實我知道 T3 的正解和 \(A\) 性質僅差一步之遙,但就虧在 T4 把時間浪費在錯解上了,或許也是前面的題花的時間過多了。
而且 T3 的表的規律或許只是運氣好,其他人都是考慮放到二維平面上考慮然後得到相同的結論。
賽後
T3 就是在 A 性質上擴充套件一下,還是 DP 考慮最終序列。
考慮除了第一段的每一段的結尾,它不能是一個隱藏的數,否則這一位填 0 以後和它不能區分,而每一段的除了結尾的位置則可以是隱藏的數。
再考慮每一段最大值的限制,它和第一段有關,如果第一段的結尾是一個隱藏的數,那麼如果後面沒有出現過最大值,那麼無法與第一段的結尾為其它數的序列區分。也就是如果第一段的結尾為隱藏的數,那麼後面一定要出現最大值。
那麼就設 \(f_{i,j,0/1}\) 表示第 \(i\) 位填了 \(j\),有沒有出現過最大值。
當 \(j\) 等於 0 時,那麼上一個數為一段的結尾,就要求上一個數不能隱藏。
總結
- 寫完程式碼要注意最佳化常數,看清資料範圍。
- 最小生成樹可以使用 \(Prim\) 或 \(Kruskal\)。稠密圖 \(Prim\) 優為 \(O(n^2)\),稀疏圖 \(Kruskal\) 優為 \(O(m\log m+\alpha(n))\)。
\(Prim\) 可以用優先佇列做到 \(O(m\log m)\)。 - 打表找規律,但不要找無意義的規律,不要在這方面浪費太多時間。
- 嘗試畫圖,把數值放到二維平面上或許能很快得到結論。
- 求經過若干操作得到最終序列有多少個,一般問什麼就從什麼方向考慮,即直接求最終序列滿足的條件,用 DP 等構造最終序列。
- 寫網路流一定要建模正確,不然假了寫這麼長的網路流就太浪費時間了。