CSP-S2019題解


格雷碼

€€£:我不抄自己辣!JOJO!

這題比那個SCOI的炒雞格雷碼好多了,甚至告訴你構造方法,所以...

void wk(uLL kk)
{
	int j=0;
	for(uLL i=n-1;~i;--i)
	{
		if(kk>>i&1) printf("%d",j^1),j^=j==0;
		else printf("%d",j),j^=j==1;
	}
}

括號樹

考慮朴素做法,即dfs整棵樹,然后記錄到根的括號序列,每個點的貢獻為祖先貢獻+以自己這個位置為右端點的合法括號序列數量,可以\(O(n^2)\)

考慮優化,我們要統計的括號序列是一段后綴,所以把)看做\(1\),(看做\(-1\),那么一個后綴為合法括號序列當且僅當這一段的權值和為0,且這個后綴沒有包含一段和\(<0\)的后綴(也就是有一些(一定匹配不上)),那么線段樹每個位置維護對應后綴的和,以及再維護區間權值0個數(就是維護區間最小值以及其個數),每次進入一個點先對所有后綴\(+1/-1\),然后線段樹上二分找出權值\(<0\)的最靠右位置\(p\),那么以這個點為最右端的合法括號序列個數就是\([p+1,dep_x]\)內權值0個數

M_sea:你個[],這不是\(O(n)\)傻[]題嗎

其實線段樹是多余的,考慮對種和開一個桶記錄這種權值的數出現次數,然后每次加入一個點就看這個點導致的整個權值變化,再用當前權值意義下為0的桶更新答案,不過要注意在之前當前權值意義下為-1的最后一個數不能計入答案,所以在更新桶的時候注意一下(ruaaa)

code

樹上的數

wtcl,又被這種題送退役了

做法顯然是從小到大枚舉每個數,然后枚舉這個數最終走到的點,如果合法就讓它走過去,現在問題是怎么判斷合法

考慮一個數從點\(x\)走到\(y\),依次經過的邊為\(e_1,e_2...e_m\),首先\(e_1\)必須比其他和\(x\)相連的邊先選(如果前面已經有一些必須在最前面的邊,就把\(e_1\)放在前面那些邊的最后面),否則這個數會走到別的地方去,並且走不回來,然后中間的\(e_i(1<i\le m)\),一定是比\(e_{i-1}\)后選,並且對於和\(e_{i-1}\)\(e_i\)公共點相連的其他邊,都不能在\(e_{i-1}\)\(e_i\)之間選.最后是\(e_m\),必須在其他和\(y\)相連的邊選完后再選(如果后面已經有一些必須在最后面的邊,就把\(e_m\)放在后面那些邊的最前面),不然這個數會跑到別的地方去

這里面比較麻煩的是中間那個其他邊不能在兩條邊之間選,但如果我們只考慮一個點以及它連出去的邊(相當於菊花圖),這個時候我們發現其他不連接這個點的邊是不會影響到這個點上的數的,也就是一個點連出去的菊花是和其他菊花互不影響的,那么可以先單獨考慮最后放在一起看.對於一個菊花,如果要使得其他邊不能在兩條邊之間選,那么這兩條邊必須選完前面一條后馬上選另一條.那么上述限制可以抽象成:一條邊必須最先選,一條邊必須最后選,兩條邊必須選完一條后馬上選另一條.那么可以對每個點建一個鏈表,維護這些邊操作的相對順序,一條邊必須在前面就建一個表示最前面的虛點,然后這條邊接在這個虛點鏈尾后面;一條邊必須在最后類似;一條邊必須在另一條邊后操作,那就把這兩條邊所在鏈接在一起.至於一些矛盾關系,這里簡單列舉一下:

  • 接完以后會出現環(無向圖意義上的環)或者鏈上有分叉
  • 最前面虛點必須為鏈首,最后面虛點必須為鏈尾
  • 接完以后使得最前面虛點為鏈首,最后面虛點為鏈尾,但是還有其他不在這一條鏈上的一些鏈

code

Emiya 家今天的飯

這里的方案計算只有某一列被選次數不超過\(\lfloor\frac{k}{2}\rfloor\)這個限制不太好直接做,所以考慮容斥,即總方案-不合法方案.總方案為\(\prod_{i=1}^{n}(1+\sum_{j=1}^{m}a_{i,j})\)

如果有一列被選次數\(>\lfloor\frac{k}{2}\rfloor\),那么有且僅有這一列會超出限制,所以不合法方案只有一列不滿足限制,那就枚舉超出限制的為哪一列,然后設\(f_{i,j,k}\)表示考慮前\(i\)行,選了\(j\)次,指定的不合法列選\(k\)次的方案,轉移背包轉移即可,那么這一列的貢獻為\(\sum_{j=1}^{n}\sum_{k=\lfloor\frac{j}{2}\rfloor+1}^{j} f_{n,j,k}\),復雜度\(O(n^3m)\)

考慮優化,如果我們把上述狀態的\(j-k\)\(k\)做差,那么一個不合法方案,它的這個差一定\(<0\),所以上述狀態改為\(f_{i,j}\)表示前\(i\)行,其他列出現次數-指定列出現次數為\(j\)的方案,然后轉移完后用\(j<0\)的統計,即可做到\(O(n^2m)\).實現時注意負下標的處理

code

划分

先考慮朴素dp,設\(f_{i,j}\)表示上一次選擇的段為\([j,i]\)的最小值,復雜度\(O(n^3)\),然后注意到一個\(f_{i,j}\),從前面一個位置\(k\)轉移過來\((k=j-1)\),那么\(f_k\)的合法第二維一定是在\([x,k]\)之間,其中\(x\)為最大的能轉移到\(f_{i,j}\)的位置,那么記一下\(f_{i,j}\)關於\(j\)的后綴最小值,即可做到\(O(n^2)\)

然后先丟一個證明鏈接,再不加證明的給出兩個引理

  • 一個\(f_{i,j}\)往后轉移時,一定是用合法的\(j\)最大的\(f_{i,j}\)的轉移到后面,后面記這個最大的\(j\)\(p_i\)
  • 對於一個\(i\),記能從前面轉移過來的位置集合為\({x}\),那么最優的轉移位置一定是集合最大值\(k\),即用\(f_{k,p_k}\)轉移到\(f_{i,k+1}\)

你問我為什么不給證明,因為這是打表得到的

這樣對於每個\(i\),二分出它最先可以貢獻的后面的位置(即最小的\(a\)滿足\(sum_a-sum_i\ge sum_i-sum_{p_i-1}\)),把\(i\)丟進對應的決策集合,然后在做到某個位置時用這個位置的決策更新最優決策,可以做到\(O(nlogn)\)

但這個二分是可以去掉的,我們維護一個單調隊列,維護還沒加入的決策\(i\),這些\(i\)以對應的\(sum_i-sum_{p_i-1}\)為關鍵字,每次加入一個決策進隊列就把隊尾的一定比這個決策差的決策彈掉(設隊尾對應元素為\(t\)\(sum_i-sum_{p_i-1}<=sum_t-sum_{p_t-1}-(sum_i-sum_t)\)時彈隊尾),然后壓進隊尾,每次用隊首能加入的決策更新當前決策

同時你還需要實現一個高精,所以請注意常數問題.我這里用的是4個\(\text{int}\)表示\(32\)位整數

code

樹的重心

初賽時認為完全二叉樹就是滿二叉樹,然后這道題以為完美二叉樹不是滿二叉樹,就GG QAQ

考慮朴素做法,枚舉鴿的是哪條邊,然后對於剩下兩個子樹分別求重心,具體操作是分別以兩個點為根,然后從根出發,走到第一個點滿足最大的兒子子樹大小\(\le \lfloor\frac{size}{2}\rfloor\).如果有兩個重心,則說明當前走到的點最大的兒子子樹大小\(=\lfloor\frac{size}{2}\rfloor\),那么另一個重心為對應子樹大小最大的兒子

如果套用以上做法,然后隨便設一個點為根,那么可以統計出鴿掉每條邊后,靠下面的點(記為\(y\))的子樹的重心貢獻,在往兒子走的時候加一個倍增優化即可.然后還有被鴿邊上面那個點(記為\(x\))的貢獻,那么可以看做是把\(x\)作為根,然后不考慮\(y\)的子樹,然后往兒子走的過程,所以可以考慮換根dfs,因為在dfs到某個點的時候,只有這個點到根路徑上的點的父子關系變化了,所以每次枚舉這個點的兒子\(y\)時,\(x\)為根時的兒子實際上是\(x\)在原樹中的其他兒子和\(x\)的父親,那么倍增數組只要用這些\(x\)為根時的兒子預處理就好了.具體實現的時候,如果\(y\)為子樹大小最大的兒子,那么\(x\)下一步走的是父親或者子樹大小次大兒子,否則是父親或者子樹大小最大兒子

code


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM