2024杭電多校覆盤 (1~5)

maple276發表於2024-09-16

因為 6 7 8 三場是我們驗的題,我基本沒補題,9 10 兩場也沒認真打,所以只覆盤了前5場。

第一場

先開01,先想到的是sam做法,結果寫到一半發現,這題記憶體只給了64M,sam開不下,於是轉行SA,過了,但是很勉強。

看了題解才發現雜湊直接秒了,怪不得這題過的人這麼多。

02 星星,就是個 n^2 的揹包,但是隊友算錯複雜度了哈哈,以為邊數是n^3。

03 是個平衡樹小練習,感覺沒啥好講的。不過題解用了線段樹合併,比我的splay少個log。我經常把這種值域合併的題當成平衡樹啟發式合併小練習,不過一般線段樹合併就夠了。

04 zxn說完題,我驚了一下,這不就是線段樹分治+可撤銷並查集的板子嗎,上學期還專門寫了部落格來著,分析了可撤銷並查集tag的標記方式。然後粘過來只改了兩行就過了。

05 隊友給出了一個非常重要的思路,那就是如果總數是偶數且字串不能平分,那麼每個人獲勝機率都是一半,雖然總是Alice先拿,不過既然是隨機的,那麼Alice和Bob的字串就是對稱的。

06 賽時沒思路,賽後補的,發現這麼簡單。非空子序列的立方和,直接轉化為:選三個序列,恰好相同的方案數。比如某個子序列出現了5次,我們把集合複製三份,每個集合中選一個,方案數就是5³。設 \(f[i][j][k]\) 表示 \(s[i]=s[j]=s[k]\) 的三個位置作為三個相同串的末尾,方案數,用 \(w[i][j][k]\) 表示三維字首和,轉移方程就一句話:\(f[i][j][k]=w[i-1][j-1][k-1]+1\),加 1 表示子序列只選這一個字元,前一個位置的字首和就是把前面選三個相同序列的所有情況都加上 i j k 三個位置。有另一個寫法,就是把序列為空當做一種情況,只選 i j k 單個字元就是從空序列的基礎上擴充一個字元,設初值 \(f[0][0][0]=1\),狀態方程改為 \(f[i][j][k]=w[i-1][j-1][k-1]\) ,麻煩的地方在於 \(f[0][0][0]\) 要計到字首和裡。

07 競賽圖三元環計數,曾經和隊友在群裡討論過,絕殺。所以說這場倆題撞槍口。求競賽圖三元環數量,只需要求出所有點的入度,答案是\((_2^n)-\sum\limits_{i=1}^n(^{d_i}_2)\)。度數用三維排序做,CDQ分治解決。

08 簽到,不是我開的。

12 是矩形並的期望面積,把矩形邊界離散化一下,整行整列切割,二維字首和求出每個小方塊兒的覆蓋次數,貢獻可以單獨算。牛客裡後來出了一道類似版本,不過那題是矩陣交,且不能為空。

第二場

01 和隊友手玩了一下,發現了規律,感覺沒什麼好說的。

02 是這場過的最難的一道,但是好像我沒參與。。。

03 是這場我們隊名次高的重要原因(university排名16),點開題目一看,三階魔方,一下子想起來川大校賽模擬二階魔方,當時二階魔方就寫了有好久,這題改成了三階,還有個角貼錯了顏色,更難模擬了啊感覺。本來我已經打算開始寫了,zxn看了看題目,問我是不是真的不用模擬,他突然腦子亮燈泡了,說只需要判斷八個角顏色的順序就行了,如果是貼錯了兩個角,那麼原本順時針的顏色順序一定會變成逆時針,好傢伙!直接很快就寫完了,不過第一發WA掉了,原因竟然是因為我程式碼裡只寫了六個角,屬實有點唐了。。。

06 小武寫的,我沒參與,不過看程式碼長度,這題不算難。

07 字串簽到,希望題目能描述得再清點。。。理解錯題意WA了一發。

08 是個很有意思的題,每個點都會分裂,且會分裂m次!簡單地猜下結論就會發現,如果確定了一條鏈作為直徑鏈,那麼第一次分裂後所有點度數就不超過3了,度數為2的點會分成兩個2,度數為3的點會分出一個3兩個2。我的思路是在樹形dp時用 \(f[i]\) 表示選擇的鏈有 i 個 3,最多能有多少個2。一開始怎麼也想不到優於n^2的做法,後來突然大膽起來,我猜能一條鏈能組出不同的3的個數平均不超過根號,這個跟去年濟南B的思路很像,只需要用unordered_map記錄能夠取到的dp值,而為什麼平均不到根號呢,其實我沒有理性證明,只是個猜想,因為想要有不同的3的個數,就必須讓這些點的度數不同,而度數越大,說明這個點的分叉越多,平均下來鏈長就會變短,大概在根號時複雜度拉滿。雖然不知道對不對,不過我還是上手寫了。可最後找最大值卻把我難住了,我們要找最大值的模數,而我只會用矩陣快速冪算出每個情況取模後的值。

zxn突然發現根本不用矩陣快速冪,而是用高中數列的知識就夠求了,設度數為 2 的點數量為 A,3 為 B,每一輪 B 不變,\(A_i=2A_{i-1}+2B\),寫成等比數列:\(A_i+2B=2(A_{i-1}+2B)\)。直接就能透過初值算結果。當m比較小的時候,可以直接求出所有值,都到不了模數,當m比較大的時候,-2B 這一個常數項就顯得非常小,我們只需要比較 \(A+2B\) 的值。

看了題解才發現自己唐了,樹形dp寫的太複雜了點。在m小的時候,每個點分裂m次的長度直接可求,算直徑不就行了;m大的時候,把 \(A+2B\) 作為第一關鍵字,\(-2B\) 作為第二關鍵字求直徑。其實題解有個更簡練的式子,就是把度數為 d 的點最終分裂的點數直接算出來了。

10 11 倆題都是小武做的,我沒看,不過都是簽到難度。

12 我們隊卡了兩個小時一點突破口沒有找到,有點過於坐牢了。賽後群裡竟然把洛谷原題連結發出來了,好傢伙,於是直接去補了洛谷上的題。

記錄n個點的和雜湊,雜湊值就是在d張圖中並查集祖先的val和,如果兩個點在d張圖中均連通,那麼他們每張圖中祖先相同,和雜湊也相同,統計多少對點雜湊值相同,就是雜湊值出現次數的平方和。用啟發式合併,兩個並查集合並時,更新size小的那個連通塊的所有點,更新它們的雜湊值。

一交,WA了,後來才知道是雜湊衝突的原因,雖然不知道是怎麼衝突的,碰撞率我也不太會算,不過把p改成自然溢位就好了,然後val值用隨機,不要固定base,很容易被卡。

隨機一個ull範圍(1.8e19)的寫法:

mt19937_64 rng(random_device{}());
val[i] = rng();

第三場

01 是個跟因子相關的dp,結論很好找,不過我是把所有因子用vector存下來了,有點卡,事實上列舉因子就能做。

02 一個線段樹合併+dp,題目風格很像DSU,不過賽時沒做出來,題解也看不懂,擺爛了。

03 透過率11%,一開始我看到這題,很是不解啊,這不是簽到難度嗎,透過率這麼低,是我理解錯題了?跟隊友討論了下,馬上就想去寫。然後被叫住了,原來這題最複雜的地方在於看到醉漢在初始位置的那些人,目擊在初始位置時間的奇偶性決定了最早出發時間。不過隊友提供了一種按時間將字首1和其他數分開記錄的寫法,細節很多,我現在不怎麼回憶得起來了,反正就是set判左右,判奇偶,寫得相當艱難且麻煩,不過最後關頭還是把它過掉了,可喜可賀。

07 線段樹pushup小練習,本來想上手寫,zxn提醒我只需要維護差分數列就行了,我恍然大悟,直接簡單了一大半。

08 小武寫的,我沒看。

11 又是一道細節題,zxn上去狂敲程式碼,有數不清的變數名,還都有用。WA了好久找我一起調,也沒調出來。最後,是哪裡錯了,竟然是答案爆了int。

12 是簽到。

唉,這場打的很魔幻,幾句話就講完了,題也沒補。

第四場

Claris場,程式碼亂搞拉滿。

05 和 09 是簽到,不需要說。

然後是 07 和 04,兩個隨機資料題。

07 數列迴圈移動,並自取max,求出每次移動後的數列和。因為資料是隨機的,我猜取最大的根號個數,迴圈移動後覆蓋的位置會很多,實時確實如此,留下的位置雖然有的很長,但是總和大概是n根號級的,再暴力求每一個留下的位置是什麼即可。拿最大的數去覆蓋,有一個區間加的操作(時間上區間加),可以差分維護。

03 的隨機化更是個重量級。

找出k個長度為質數的子段,使子段和的min值最大。現在都不知道自己程式碼在隨機資料下的複雜度,是使勁卡卡過去的,6s的題卡了又卡到了5s過了。

先二分一個答案h,然後線段樹維護字尾,二分找第一個是質數且字尾大於h的位置,有點神仙。。。題解是用的set,不知道和線段樹有什麼區別,不過肯定比我們的程式碼快。

然後12又是倆隊友寫的,我看都沒看。

唉感覺這兩場都沒什麼好講的,過題比較少。

做完 03 和 12 就沒時間看 06 了,一開始想的是這個題爆搜剪枝,反正m只有50,不過 4^50 有點太大了。

群裡有人說dp,jiangly也親自討論起了複雜度,雖然這題我們沒補(我沒有hdu交題賬號,一點補題慾望都沒),不過想了想好像已經會了。dp記錄自己的位置,敵人的位置,敵人的血量,列舉時多一個m,這個複雜度可過。和隊友討論了下dp和記憶化搜尋的區別,結論是沒有區別,不過我們沒想到狀態怎麼設定,也就是沒想到直接記錄自己和敵人位置就行了,不需要關心操作序列長什麼樣,這樣可以壓縮掉大量的狀態。

第五場

02 是個小分類討論,好像情況挺多的,忘了。

04 比較有意思,首先是 \(lcm(i,j)<=x\) 這個耐人尋味的式子,本以為是數學,但是小武在做 05,沒空來幫忙,後來我猜想了下,是不是 n 以內的 i j,lcm 小於 n 的數量會比較少,模擬了一下,發現只有百萬級。題解裡說了大概是 nlog^2 ,不過其實目測也只有單個 log。

於是我想到了一個線段樹合併的做法,權值線段樹記錄每個值 x 的出現次數。首先將 i j 的貢獻合併到 LCA 處,列舉 ij 也比較有講究,為了保證 lcm 小於 n,我先列舉gcd,再列舉 \(x=\frac i{gcd},y = \frac j{gcd}\),x·y·gcd 就是 lcm,雖然還需要判斷 xy 是否互質,不過總體複雜度也是倆log的,線段樹合併也是倆 log 的,我以為已經做完了。

結果給線段樹開空間的時候意識到不對勁了,這個空間複雜度是倆 log 的,但這題只給 256M,丸辣。

但是這題也不能就這麼放棄啊,算了下空間的極限,我嘗試性地去開了132倍空間,乞求資料比較弱,然後一直不斷地減小常數,因為線段樹合併的寫法配上倆log的演算法,常數是有點大,這題的評測狀態也在 TLE 和 MLE 之間不斷盤旋。

最後,4s的題,3968ms跑過去了,我只能說很6。賽後想了想,這題資料卡滿還是挺難的。

正解是什麼呢,我們注意到,這題我們用權值線段樹來統計小於 x 的值,但是為啥這題只問小於 x,而不是問 l 到 r 之間呢(不對。。我錯了,改成 l 到 r 之間也能做)。題解是將詢問按 x 離線排序,然後一個一個值往樹上加,而將樹上dfn序形成的區間用一個線段樹來維護。挺妙的,這就省去了線段樹合併空間上的log。本來以為題目改成 l 到 r 的話需要套莫隊的,又想了想好像不用,就把每個詢問拆成 r 和 l-1 兩個詢問,最後答案做個差就行了。


05 好像卡掉了虛樹的做法,小武用虛樹搞的,賽後反思了下好像只需要用並查集加個小最佳化就行了,而且不需要建虛樹,吃完飯回來就補了,我沒仔細想。

06 打了個小表猜結論,規律是發現了,不過賽時沒考慮證明,吃飯的時候把證明給補上了。如果三個數都是1那就必敗,歸納完發現三個數都是奇數直接必敗,奇偶不同則必勝,但是三個數都是偶數還沒發現規律。思考了一下發現,因為要先吃掉一個數,然後再劈開另一個數,那麼劈開之後不能是兩個奇數,否則三個數奇偶不同了,所以劈開也只能是偶數,那就相當於什麼,就是兩個兩個繫結,操作的粒度都是偶數,三個數都除以二的答案也是一樣的。所以輸入的三個數都是偶數的話,一直讓它們除以2,看最後是三個奇數還是奇偶不同。經過牛客那次比賽後,懂了一個更快的寫法,就是判斷三個數lowbit是否相同。


08 貓咪們狂歡,一道有好多種建圖方法的流題。

最好想的是zxn提出來的,如果選了一條邊,那麼就不能選另一棵樹上同一個點的相鄰邊,兩邊的邊可以分成左右兩個陣營,且陣營內部不存在衝突,因此可以直接套用最大權獨立集:將所有可以狂歡的邊連向源匯點,流量設為狂歡值,當然那些狂歡不了的邊就不用理睬了,加上衝突邊後跑最小割,最大獨立集為可以狂歡的總權值減去最小割。

這個建圖方式是有複雜度缺陷的,因為 n 取值是1000,如果兩棵樹都是菊花圖,那麼第一棵樹的每一條邊都和另一棵樹的所有邊衝突,因此二分圖中間的邊數達到了驚人的n^2,也就是1e6條邊,何況這題還是多測,n 總和 10000,最大權獨立集的做法顯然不是正解。

那麼題解是怎麼建圖的呢?題解轉化為了一個最大權閉合子圖的模型。

首先讓所有貓咪走到第一棵樹上(最大權閉合子圖經典套路,然後開始考慮移動的代價),算出此時的總和 sum1。對於任何一隻狂歡貓,如果它要蹦躂到第二棵樹上,那麼第一棵樹與它相鄰的所有邊失去狂歡值(需要是鄰點有狂歡貓的邊),因此開 k 個負權的點表示一隻狂歡貓從第一棵樹跳到第二棵樹這一事件。首先處理第二棵樹,如果第二棵樹上一條邊兩個點都跳來了狂歡貓,那麼加上這條邊的邊權,將這條邊設成一個正權的點,如果同時選那兩隻貓的點,那麼就可以獲得這個正邊權的點。怎麼在最大權閉合子圖模型裡描述選了兩個點就可以選另一個點呢?我們可以反過來理解為選了另一個點就必須選這兩個點,因為閉合子圖邊的關係是必須而不是可以,因此這個正權的點連向那兩隻貓。第一棵樹上的邊也有問題,一條邊兩邊的狂歡貓都跳走了,那麼這條邊權值被減了兩次,我們補償一個正權點,表示同時跳走這兩隻貓,就可以額外多獲得這條邊的權值,建圖和第二棵樹上的邊一樣,你會發現甚至是對稱的。根據最大權閉合子圖的計算公式,我們設正權點總權值和為 sum2,最小割為 maxflow,答案就是 sum1+sum2-maxflow,sum1 是第一棵樹所有狂歡邊的和,sum2 是第一棵樹加第兩棵樹所有狂歡邊的和,為啥建邊是對稱的但是第一棵樹與第二棵樹卻不對稱了呢,因為狂歡貓的那些點負權值是和第一棵樹有關的。

這樣以來,邊數,點數都是 O(n) 的,只能說很妙。


11 和 13 感覺都有點籤,最後講一下 10。

10 其實是一道歪榜題,因為題面太長沒人想去讀,和牛客第6場那道挖掘機狀況很像。

一開始我在想每個點維護根號個祖先的和,但是將某點權值置為零的修改操作會導致修改一大堆的值,至於那些攢一部分再修改的方法,都想過了,但因為修改和詢問操作是疊在一起的,且詢問操作也需要修改,所以放棄了根號演算法,以為這題挺難的。

結果zxn突然發現,好像這題是個水題,因為每個點的莊稼只會被收割完一次,那麼我們倍增找到第一個沒被收割完的點,向下收割就可以了,當時距離比賽只有20分鐘了,我二話不說開始敲起了倍增程式碼,然後又討論起了置零操作會不會干擾倍增的尋找過程,討論後的處理方式是打個標記,當準備收這個點的莊稼的時候突然發現這個點已經被蝗蟲吃掉了,就繼續找下一個點。

敲程式碼敲到一半問題又來了,下一個點怎麼找?

每個點有許多兒子,我怎麼知道哪個兒子連向的是詢問的起點呢?正當我準備寫個 dfn 序加 vector 上二分時,zxn跟我說,如果當前點只有一個兒子,就可以直接下去,如果有多個兒子,那麼我們從起點再重新倍增一次。這個想法我覺得很天才也很對,雖然複雜度不知道,不過我先寫了。

在距比賽結束5分鐘的時候,我寫完了程式碼,測一發樣例沒過,我沒慌,我覺得我程式碼錯誤不多,然後仔細審閱了一番,發現了一個小錯誤,改完之後樣例第一組過了,第二組沒過,又發現是多測沒清空 vector。

過樣例了,看時間 59 了,直接交,然後直接過了,非常薊縣,16:59:34,20分鐘帶思考帶敲程式碼帶debug,腦子要炸掉了,看到AC了之後馬上歇了下來。

本來以為拿了個頑強拼搏,結果一看提交記錄還有最後十秒過題的,甚至是同一個題,自愧不如哈哈。