JSOI2019 Round2 做題筆記
JSOI的題質量很高……
精準預測(2-SAT、拓撲排序、bitset)
不難發現兩個條件都可以用經典的2-SAT連邊方式連邊,考慮如何加入時間的限制。對於第\(x\)個人在\(t\)時刻的狀態是生/死建點\((x,0/1,t)\),連上邊\((x , 0 , t) \rightarrow (x , 0 , t-1)\)和\((x,1,t) \rightarrow (x , 1 , t+1)\)然後用2-SAT方式連邊,就可以加入時間限制了。
上面的點數實在是太多了,一種樸素的想法是把所有2-SAT邊連接的點拿出來建圖。這樣總點數是\(4m\)的,在LOJ上可以跑過,但是在Luogu上過不去……
還有一種相對更優秀的建圖:對於2-SAT中的邊\((x,y)\)
不難發現我們連出來的圖是一個拓撲圖(生狀態之間是一個拓撲圖、死狀態之間是一個拓撲圖、跨越生死的邊均是生連向死),所以我們的問題變成了對於每一個\((x,0,T+1)\)求出它能夠到達的所有\((y,1,T+1)\)的狀態數。在反圖上拓撲排序+bitset。bitset開不下空間,於是分批做拓撲排序即可,復雜度不變。
註意可能存在一個人生連向死的情況,這個要特殊判斷一下。
如果過不去開O3試一下……
代碼
神經網絡(樹形DP、容斥、生成函數)
這似乎是某道模擬賽題……
題目等價於:每一次可以在一棵樹上選一條鏈(鏈上的點和之前經過的點無交),然後再在其他任意一棵樹上選,問選到所有點的方案數
把這道題分為兩個部分:將一棵樹分為若幹不相交的鏈、將這些鏈安排順序使得不存在兩條相鄰的鏈來自同一棵樹。
因為第二部分的方案數只和第一部分中每棵樹劃分出的鏈的條數有關,所以要求對於每一個\(j\),將當前樹劃分為\(j\)條鏈的方案數。不難想到一個樹形DP:設\(f_{i,j,0/1/2}\)表示\(i\)及其子樹中共有\(j\)條鏈,\(i\)點向兒子連出\(0/1/2\)條邊的方案總數,轉移考慮兒子和當前點的鏈是否合並。
值得註意的是:\(1\)個點也是一條鏈;對於長度\(>1\)的鏈,它對答案的貢獻為\(2\),因為對於鏈的兩個端點\(AB\),可以\(A\)進\(B\)出,也可以\(B\)進\(A\)出,在DP的時候需要乘上這個系數。
然後考慮第二部分。設第一部分求出來的答案為\(f_{i,j}\)表示將第\(i\)棵樹分成\(j\)段的方案數\(\times j!\),因為鏈有序。接下來只需要考慮相鄰鏈不來自同一棵樹的方案。
先考慮序列上的問題,我們選擇指數型生成函數求解。假設將第\(i\)棵樹劃分成\(k\)條鏈,那麽在序列上會有\(k-1\)個空。容斥有多少個空在序列合並後不存在,不難得到這一項的值為\(f_{i,k} \sum\limits_{j=1}^k (-1)^{(k-j)} \binom{k-1}{j-1} \frac{x^j}{j!}\),那麽這一棵樹的生成函數就是\(\sum\limits_{k=1}^{k_i} f_{i,k} \sum\limits_{j=1}^k (-1)^{(k-j)} \binom{k-1}{j-1} \frac{x^j}{j!}\)
然後考慮環上。我們選出一棵樹作為基準樹,即斷環成鏈之後,序列起始的一條鏈一定來自這棵樹。那麽其他樹的生成函數不變,這棵樹由於第一條鏈的位置確定,所以生成函數變為\(\sum\limits_{k=1}^{k_i} \frac{f_{i,k}}{k} \sum\limits_{j=1}^k (-1)^{(k-j)} \binom{k-1}{j-1} \frac{x^{j-1}}{(j-1)!}\),在\(f_{i,k}\)處除\(k\)的原因是將\(i\)樹劃分為\(k\)條鏈後這\(k\)條鏈都可以作為序列的起始位置。
還有一個限制是基準樹的第一條鏈和最後一條鏈之間不能相接。所以基準樹的生成函數還需要減掉\(\sum\limits_{k=1}^{k_i} \frac{f_{i,k}}{k} \sum\limits_{j=1}^k (-1)^{(k-j)} \binom{k-1}{j-1} \frac{x^{j-2}}{(j-2)!}\)。
最後暴力把這些函數卷積就可以得到答案。復雜度\(O(n^2)\)。
代碼
節日慶典(最小表示法、Z-algorithm)
我們要求每一個前綴的最小表示法,然後這道題就跟最小表示法沒什麽關系了。我們使用增量法求解,也就是每一次加入一個字符,然後在新的串上快速求解。
對於當前正在考慮的前綴\(s[1,l]\)的兩個後綴\(i,j(i < j \leq l)\),若\(LCP(suf_i , suf_j) < l - j + 1\),那麽後綴較大的那個位置不可能作為最小表示法的位置;如果\(LCP(suf_i , suf_j) \geq l - j + 1\),那麽兩個位置都有可能作為最小表示法的位置。我們維護集合\(S\)滿足\(\forall a,b \in S , a < b , LCP(suf_a , suf_b) \geq l - b + 1\),就可以將答案鎖定在\(S\)內。但是\(S\)的元素個數可能仍然很多,所以仍需優化。
考慮對於\(S\)中的兩個數\(a,b\)滿足\(l-b+1 < l-a+1 < 2(l-b+1)\)。由於\(s[b,l]\)是\(s[a,l]\)的前綴、是\(s[a,l]\)的後綴,也就是\(s[a,l]\)的長度\(> \frac{l-a+1}{2}\)的border。這意味著\(s[a,l]\)有一個長度為\(b-a\)的周期,設這個周期串為\(T\)。這同時意味著\(s[a,l]\)有一個比\(l-b+1\)更短的border?,記作\(s[c,l]\),不難發現\(c\)的一個可行值為\(2b-a\)。
因為串循環相當於在當前串的末尾插入當前串開頭對應的字符,所以考慮在\(s[a,l],s[b,l],s[c,l]\)之後加入字符會得到怎樣的解。如果加入的字符\(x = T[0]\),令\(T = T[1,l] + T[0]\),繼續往後做;如果\(x < T[0]\),則\(s[c,l]+x < s[b,l]+x < s[a,l] + x\),選擇\(c\)更優;如果\(x > T[0]\),則\(s[c,l]+x > s[b,l]+x > s[a,l] + x\),選擇\(a\)更優;如果匹配到最後都沒有匹配出三者的大小關系,因為\(a<b<c\),所以選擇\(a\)更優。這意味著\(b\)無論如何不可能最優,可以直接把\(b\)刪掉。
按照上面的敘述操作之後,\(\forall a,b \in S , a<b \rightarrow l - a + 1 \geq 2(l - b + 1)\),那麽\(S\)的元素個數只有\(O(logn)\)個。而我們可以在增量的時候方便地維護集合\(S\)。
接下來考慮如何通過\(S\)求出答案。我們需要做的就是對於兩個位置\(a,b(a<b \leq l)\)求出兩種循環表示的串的字典序大小。從\(a\)開始的循環串分為\(s[a,l]\)和\(s[1,a-1]\),從\(b\)開始的循環串分為了\(s[b,l]\)和\(s[1,b-1]\),而由\(S\)的定義可知\(s[a,a+l-b] = s[b,l]\),所以我們只需比較\(s[a+l-b+1,l]\)與\(s[1,b-a]\)之間的關系、\(s[1,a-1]\)與\(s[b-a+1,b-1]\)之間的關系,也就是求出\(LCP(suf_1,suf_{a+l-b+1})\)和\(LCP(suf_1,suf_{b-a+1})\)。
註意到上面的LCP的計算都和原串有關,所以可以在\(O(n)\)時間內用Z-algorithm求出每一個後綴和原串的LCP,就可以快速比較兩個位置。
復雜度是不滿的\(O(nlogn)\)。
代碼
JSOI2019 Round2 做題筆記