IOI 2020 集訓隊作業胡扯「1, 50」
IOI 2020 集訓隊作業胡扯「51, 100」
IOI 2020 集訓隊作業胡扯「101, 150」(★)
如果您點擊了某個超鏈接,但是並沒有發生任何變化,這意味着您可能需要在另外兩章節中尋找對應內容。
表格
綠的表示主要沒看題解,紅的表示主要看了題解。
2020-02-29
agc028_d
令 \(n = 2N\)。
把圓上看成序列上也可以,也就是說如果 \([l_1, r_1]\) 和 \([l_2, r_2]\) 相交但不包含,那么轉化到圓上他們就是相交的。
考慮對於每個連通塊,計算它對答案的貢獻次數,加起來就是答案。
我們發現一個連通塊在序列上一定對應了恰好一個區間,滿足連通塊完全在這個區間內,而且區間端點屬於連通塊。
但是要注意並不是這個區間內全都是這個連通塊,可能其中還有小連通塊。
則令 \(dp(i, j)\) 表示區間 \([i, j]\) 內任意連邊,且 \(i, j\) 在同一連通塊內的區間內連邊方案數。
枚舉這個區間 \([i, j]\),它的長度一定為偶數。則可以發現這個區間內的點不能向區間外連邊,這是有利於統計的。
如果區間內任意連邊,那么方案數就是 \(dp(i, j) = g(c(i, j))\)。
其中 \(g(x)\) 表示 \(x\) 個點的環任意連邊的方案數,當 \(x\) 為偶數時等於 \(1 \cdot 3 \cdot 5 \cdot \cdots \cdot (x - 1)\),否則為 \(0\)。
而 \(c(i, j)\) 為 \([i, j]\) 內還沒確定連邊情況的點的個數。
但是還有第二個條件,就是 \(i, j\) 在同一個連通塊內。考慮容斥,如果不在,枚舉 \(i\) 連通塊的區間右端點 \(k\)。
有 \(\displaystyle dp(i, j) = g(c(i, j)) - \sum_{k = i}^{j - 1} dp(i, k) \cdot g(c(k + 1, j))\)。
最后求出所有 \(dp(i, j)\) 后,就有答案等於 \(\displaystyle \sum dp(i, j) \cdot g(n - 2K - c(i, j))\)。
時間復雜度為 \(\mathcal O (n^3)\),評測鏈接。
2020-03-01
agc029_f
可以發現,選擇一部分給定的 \(E_i\),即選出 \(\mathcal{E} = \{E_1, E_2, \ldots , E_{N - 1}\}\) 的一個子集 \(\mathcal{S} \subseteq \mathcal{E}\)。
考慮 \(\mathcal{S}\) 能夠連通的所有點,令它們為 \(f(\mathcal{S})\),即 \(f(\mathcal{S}) = \{u \mid u \in E_i \in \mathcal{S}\}\)。
如果 \(\mathcal{S}\) 非空且 \(|f(\mathcal{S})| \le |\mathcal{S}|\),那就完蛋了,因為不管怎么使用這 \(|\mathcal{S}|\) 條邊,都會連出環來,造不成樹。
也就是說,對於任意 \(\mathcal{S} \ne \varnothing\),都必須要有 \(|f(\mathcal{S})| \ge \mathcal{S} + 1\)。
這是一個有解的必要條件,之后的構造顯示了它也是充分條件。
看起來很 Hall 定理,先整個二分圖匹配再說吧。
二分圖左邊為 \(N\) 個點,右邊為 \((N - 1)\) 個點集,如果 \(u \in E_i\),則左邊的 \(u\) 對應的點與右邊的 \(E_i\) 對應的點之間連邊。
這樣必須要有右側的 \((N - 1)\) 個點的完備匹配,如果求不出來一定無解,可以使用 Dinic 實現。
那么左邊就會有恰好 \(1\) 個點沒被匹配到,令它為 \(r\)。
考慮從 \(r\) 開始 DFS,隨便找到一個與 \(r\) 相鄰的,且未被經過的右側的點 \(i\),把它標記為被經過了,令這個點在左側的匹配為 \(u\),然后就能確定 \(E_i\) 中選擇的兩個點是 \(r\) 和 \(u\)。然后 DFS 遞歸進 \(u\),這樣一直下去直到把所有點都經過為止。
如果還沒有經過所有點,就無法拓展新點而返回了,那就是無解。
這個過程有沒有可能把有解判成無解呢?
如果過程中途停止了的話,當且僅當:對於所有的左側被經過的點,與它們相鄰的右側的點都被經過了,無法拓展新點。
顯然左側被經過的點數在任意時刻都等於右側被經過的點數 \(+ 1\),因為多了一個 \(r\)。
考慮令 \(\mathcal{S}\) 為右側未被經過的點,它們在左側對應的點也是未被經過的,那么 \(|f(\mathcal{S})| \le |\mathcal{S}|\),也就是無解,與假設矛盾。
所以一定不會把有解判成無解,如果無解一定是 \(f(\mathcal{S})| \le |\mathcal{S}|\) 的情況出現了。所以 \(|f(\mathcal{S})| \ge \mathcal{S} + 1\) 是充分必要條件。
時間復雜度為 \(\mathcal O (N + M \sqrt{N})\),評測鏈接。
2020-03-03
cf674G
如果 \(p = 50 + \varepsilon\) 會怎么樣?
這便是一個經典問題:長度為 \(n\) 的數列中存在一個出現次數大於 \(n / 2\) 的數,設計一個算法找到它。
只要每次刪除兩個不同的數,最后留下的那個數(或那些數,但這些數全部相同)就是要求的答案。
原理是,如果一個數出現了 \(a\) 次,其中 \(a > n - a\),則兩邊都減去 \(1\),仍有 \(a - 1 > n - a - 1 = (n - 2) - (a - 1)\)。
對於拓展情況我們如法炮制,令 \(\displaystyle k = \left\lfloor \frac{100}{p} \right\rfloor\)。每次刪除 \(k + 1\) 個數,則對的數一定會留下來。
因為 \(k\) 最大就是 \(5\),建立一棵線段樹,使用每次 \(\mathcal O (k^2)\) 的時間合並區間,不難維護答案。
時間復雜度為 \(\mathcal O (n k^2 \log n)\),評測鏈接。
2020-03-04
agc030_f
也就是這 \(2 N\) 個數兩兩配對,然后每一對中的較小數會進到 \(B\) 里面去。
把已經確定有配對的(\(A_{2 i - 1}, A_{2 i} \ne -1\))扔了,剩下的是一對里面已經有一個確定了的,和兩個都沒確定的。
我們把 \(1 \sim 2 N\) 排成一排,配對的連一條線,那么:
\(B\) 中的元素就是每條線的左端點的值,但是 \(B\) 里還有順序,這該怎么辦呢?
考慮:如果一條線的某一端的值,在 \(A\) 中是存在的,也就是說這一對的一端已經是確定的,那么在 \(B\) 中的位置也是確定的。
如果這條線兩端的值都沒有在 \(A\) 中,那么我們先不給這條線賦值,等到最后統計完所有方案后,可以發現這樣的線的個數是確定的(就等於 \(N\) 減去一端在 \(A\) 中的數對的數量),假設為 \(S\),把答案乘以 \(S!\) 就行了。
那么,也就是說,我們需要統計連線方案數,兩個方案不同當且僅當某個位置在其中一個方案中是線的左端點,而在另一個方案中不是,或者某個左端點所在的線的標號不同(如果這條線的一端在 \(A\) 中存在)。
記 \(v_i\) 表示值 \(i\) 是否在 \(A\) 中存在。
則考慮從大往小 DP,設狀態 \(dp(i, j, k)\) 表示已經考慮了 \(\ge i\) 的數值,其中有 \(j\) 個 \(v_x = 0\) 的右端點 \(x\) 還沒配對,其中有 \(k\) 個 \(v_x = 1\) 的右端點 \(x\) 還沒配對。
可以發現 \(v_x = 0\) 的可以匹配 \(v_y = 0\) 或者 \(1\) 的,但是 \(v_x = 1\) 的只能匹配 \(v_y = 0\) 的。
則有轉移:\(dp(i + 1, j, k)\) 可以轉移給:
- \(v_i = 1\):\(dp(i, j - 1, k)\),系數為 \(1\);表示匹配了更大的一個 \(v_x = 0\) 的。
- \(v_i = 1\):\(dp(i, j, k + 1)\),系數為 \(1\);表示要向更小的值匹配。
- \(v_i = 0\):\(dp(i, j - 1, k)\),系數為 \(1\);表示匹配了更大的一個 \(v_x = 0\) 的。
- \(v_i = 0\):\(dp(i, j, k - 1)\),系數為 \(k\);表示匹配了更大的一個 \(v_x = 1\) 的,注意這里要乘以 \(k\) 因為可以任選一個,且每一個帶來的這條線的標號都不同。
- \(v_i = 0\):\(dp(i, j + 1, k)\),系數為 \(1\);表示要向更小的值匹配。
時間復雜度為 \(\mathcal O (N^3)\),評測鏈接。
cf698D
我們單獨對每個目標 \(i\) 考慮它是否能被射擊到,然后求和即可得到答案。
考慮一個射擊到目標 \(i\) 的順序,它的最后一發子彈會擊中目標,而之前的子彈都是在清理障礙。
設最后一發子彈是從射擊點 \(j\) 射向目標 \(i\) 的,把路徑上的障礙一一列出。
則枚舉一個障礙 \(x\),使用另一個射擊點 \(y\) 把這個障礙擊中。
則我們稱從 \(y\) 射擊擊中障礙的“目的”是為了給 \(j \to i\) “清障”,然后連一條從 \(y\) 到 \(j\) 的有向邊。
從 \(y\) 射向 \(x\) 的路徑上可能還有障礙,這樣下去把所有的“目的”鏈連出來。
容易發現“目的”關系連成了一棵 DAG(這是因為某一次“清障”可能會讓兩個或更多的未來的射擊受益)。
但是如果我們為目標排個序,優先考慮優先級更大的目標,則這個 DAG 可以看成一棵有序樹。
也就是說同一個節點下的子樹,排在前面的子樹可以影響排在后面的(前面的“清障”會對未來產生影響)。
那么我們枚舉這棵樹的 DFS 序,然后每次令 DFS 序的第一個數擊中要求目標,然后遞歸考慮障礙即可。
遞歸層數最多為 \(k\) 層,所以時間復雜度為 \(\mathcal O (n \cdot k! \cdot k)\)。
要注意的是障礙的判斷,如果用極角序排序的話要注意精度,我使用 double
和 atan2
被出題人卡精度了。
時間復雜度為 \(\mathcal O (n \cdot k! \cdot k)\),評測鏈接。
2020-03-05
agc021_e
既然是對球的顏色序列計數,那么就只需要考慮,對於每一個顏色序列,判斷它是否可行。
假設總共喂了 \(R\) 個紅球和 \(B\) 個藍球,滿足 \(R + B = K\)。
首先考慮一只變色龍在什么情況下最終才會變成紅色:
- 它吃的紅球比藍球多。
- 它吃的紅球和藍球一樣多(且不為 \(0\) 個),但是吃的最后一個球是藍球。
如果 \(R < B\),也就是紅球總數比藍球少,那么顯然無解。
如果 \(R \ge B + N\),也就是紅球比藍球還多 \(N\) 個,可以直接讓每只變色龍吃的紅球都比藍球多,一定有解。
否則 \(B \le R < B + N\),就只能讓一些變色龍吃的紅球和藍球一樣多,另一些變色龍紅球吃的更多一些。
對於一種方案,我們進行調整:
- 如果某一只變色龍吃的紅球比藍球多兩個以上,可以拿出一個紅球分配給別的變色龍。
這樣調整之后,就一定是:一些變色龍吃的紅球比藍球多恰好一個,另一些一樣多。
令 R
表示一個紅球,B
表示一個藍球。
也就是恰好有 \(R - B\) 只變色龍多吃一個 R
,其它 \(N - (R - B)\) 只變色龍吃的一樣多。
如果 \(R = B\),也就是 \(R - B = 0\),不存在多吃一個 R
的變色龍,那么考慮吃掉了最后一個球的變色龍,那么最后一個球必然是 B
,那么去掉這個藍球,就等價於 \((R, B - 1)\) 的情況。
那么現在就至少有一只變色龍多吃一個 R
,對於吃的一樣多的變色龍,可以再調整:
如果它吃的不是恰好兩個球(RB
),那么可以在最后一個字符前取出一個 R
和一個 B
,分給多吃了一個 R
的變色龍。
那么現在就變成了,每只吃的紅球和藍球一樣多的變色龍,吃球的順序都是 RB
,它們的個數為 \(N - (R - B)\)。
然后剩下的 \(R - B\) 只變色龍,就可以直接每只多吃一個 R
然后滿足條件了。
綜上所述:對於 \(B < R < B + N\) 的情況,滿足條件當且僅當能夠取出 \(N - (R - B)\) 對 RB
(有順序)的子序列。
這等價於:對於每個前綴,這個前綴中的藍球比紅球最多多 \(B - (N - (R - B)) = R - N\) 個。
(就是說這個前綴中無法參與匹配的藍球數量不能超過 \(R - N\) 個)
換句話說,令 R
為 \(+1\),B
為 \(-1\),則任意一個前綴和都應該要大於等於 \(-(R - N)\)。
可以看成從 \((0, 0)\) 向右(R
)或向上(B
)走到 \((R, B)\),但是不能到達直線 \(y = x + (R - N)\) 的嚴格上方。
這是一類經典的組合問題,類似於卡特蘭數的計算方法(翻轉第一次碰到直線上方的部分),可以得到答案:
枚舉 \(R\)(\(B = K - R\))計算組合數即可得到答案。
時間復雜度為 \(\mathcal O (K)\),評測鏈接。
agc032_e
先把 \(a\) 從小到大排序,對於每一對 \((i, j)\),把 \(i, j\) 用一條線連起來。
如果 \(a_i + a_j < M\),則用藍色線,如果 \(a_i + a_j \ge M\),則用紅色線。
那么我們可以證明,一定存在一種最優情況,滿足:
- 存在一個分界點,使得它左右兩側沒有匹配,也就是沒有連線經過分界點。
- 只考慮分界點左側,則最小的數和最大的數連線,第二小的數和第二大的數連線,以此類推。
- 分界點右側也是一樣,最小的和最大的連線。
- 分界點左側的線的顏色都是藍色,分界點右側的線的顏色都是紅色。
用圖表示就是這樣:
怎么證明它是對的呢?可以使用調整法。
考慮兩個數對,如果它們同色但不包含,或者它們異色但不相離,則可以調整成滿足條件且不更劣的情況。如圖:
令四個數從左到右為 \(a, b, c, d\),注意到 \(a \le b \le c \le d\)。
以及兩個不等式:對於 \(x, y\)(\(x \le y\)),它們之間的線的權值 \(w\) 滿足:
如果線是藍色的,則 \(w \ge y\);如果線是紅色的,則 \(w < x\)。
反復使用這些不等式,即可得出調整后的情況不會更劣的結論。以右側第三種情況為例:
- 令線 \(a \sim d\) 的權值為 \(x\),線 \(b \sim c\) 的權值為 \(y\)。
- 因為 \(y \ge c \ge a > x\),所以這種情況的權值較大值為 \(y\)。
- 考慮調整后的情況,因為線 \(b \sim c\) 變成了 \(b \sim a\),而 \(a \le c\),所以線還是藍色的。
- 因為線 \(d \sim a\) 變成了 \(d \sim c\),而 \(c \ge a\),所以線還是紅色的。
- 接下來證明權值較大值不會變得更大:
- 考慮線 \(a \sim b\),其權值為 \(a + b \le b + c = y\)。
- 考慮線 \(c \sim d\),其權值為 \(c + d - M < c \le b + c = y\)。
- 所以權值都不會變得更大,證畢。
其他情況的證明類似。
那么得到了這個結論后,如何計算答案呢?如果枚舉分界點,然后計算答案是 \(\mathcal O (N^2)\) 的,不能接受。
我們弱化一下條件:每條線 \(x \sim y\) 都可以染藍色,權值為 \(x + y\),但是要染紅色時必須有 \(x + y \ge M\)。
則可以發現:在滿足條件的情況下,分界點越往左,答案越優,但是如果太往左了,就會導致分界點右側不滿足條件。
那么我們二分一下這個分界點即可。
時間復雜度為 \(\mathcal O (N \log N)\),評測鏈接。
2020-03-07
cf671E
先定義兩個數組 \(\mathrm{pre}[i]\) 和 \(\mathrm{suf}[i]\):
\(\mathrm{pre}[i]\) 表示:從 \(1\) 出發到 \(i\) 需要花費多少單位的油(假設開始時油量充分大)。
\(\mathrm{suf}[i]\) 表示:從 \(i\) 出發到 \(1\) 需要花費多少單位的油(假設開始時油量充分大)。
則有 \(\mathrm{pre}[i] = \mathrm{pre}[i - 1] - g_{i - 1} + w_{i - 1}\);
並且 \(\mathrm{suf}[i] = \mathrm{suf}[i - 1] - g_i + w_{i - 1}\)。
則從 \(i\) 向右走到 \(j\),需要花費的油量就是 \(\mathrm{pre}[j] - \mathrm{pre}[i]\)。
則從 \(j\) 向左走到 \(i\),需要花費的油量就是 \(\mathrm{suf}[j] - \mathrm{suf}[i]\)。
然后我們考慮去計算一個區間 \([l, r]\) 的代價 \(\boldsymbol{w(l, r)}\):這個區間內需要給 \(\boldsymbol{g}\) 增加多少才可以往返通行。
如果 \(w(l, r) \le k\),則這個區間就可以作為答案了。
先考慮從左往右開,從 \(l\) 一直開到 \(r\),中間可能會遇到一個位置開不過去了,令這個位置為 \(\mathrm{next}[l]\)。
也就是說從 \(l\) 一直往右開,最多只能開到 \(\mathrm{next}[l] - 1\) 的位置,而開不到 \(\mathrm{next}[l]\)。
則這個位置就必須滿足 \(\mathrm{pre}[\mathrm{next}[l]] > \mathrm{pre}[l]\),而且 \(\mathrm{next}[l]\) 必須是滿足這個條件的第一個位置。
也就是說 \(\boldsymbol{\mathrm{next}[i]}\) 為滿足 \(\boldsymbol{\mathrm{pre}[i] < \mathrm{pre}[k]}\) 且 \(\boldsymbol{i < k}\) 的第一個 \(\boldsymbol{k}\),可以通過單調棧求出。
那么我們考慮令 \(\mathrm{val}[i] = \mathrm{pre}[\mathrm{next}[i]] - \mathrm{pre}[\mathrm{next}[i] - 1]\),這也就是想要到達 \(\mathrm{next}[i]\) 需要給 \(g\) 增加的最小權值。
也就是說:想要到達 \(\mathrm{next}[i]\),就必須在 \(g_l \sim g_{\mathrm{next}[i] - 1}\) 之間增加至少 \(\mathrm{val}[i]\),具體加在哪里沒有影響。
但是考慮到我們還要回來,就是說要從 \(r\) 向左回到 \(l\),那么這個 \(\mathrm{val}[i]\) 還是加在盡量靠右的位置比較好。
也就是說 \(\mathrm{val}[i]\) 一定會全部加在 \(g_{\mathrm{next}[i] - 1}\) 上,也就是說 \(g_{\mathrm{next}[i] - 1} \gets g_{\mathrm{next}[i] - 1} + \mathrm{val}[i]\)。
這樣就能在最優情況下成功到達 \(\mathrm{next}[i]\) 了,且此時油箱是空的,就可以繼續處理 \(\mathrm{next}[i] \to r\) 的子問題了。
這樣處理完之后,可以求出從 \(l\) 到 \(r\) 的最小代價,令它為 \(\boldsymbol{\mathrm{cost}(l, r)}\)。
但是此時不一定能成功從 \(\boldsymbol{r}\) 回到 \(\boldsymbol{l}\)。
因為只需要考慮從 \(r\) 回到 \(l\) 的情況,所以這時候要是回不來,直接把所有需要加的油都在 \(g_r\) 上加滿就行了。
也就是說,剛剛從左到右的過程,會給 \(g_i\) 造成一些影響(\(g_{\mathrm{next}[i] - 1} \gets g_{\mathrm{next}[i] - 1} + \mathrm{val}[i]\))。
進而會影響到 \(\mathrm{suf}[i]\) 的值,令被影響的新的 \(\mathrm{suf}\) 叫做 \(\mathrm{suf}'\)。
則從 \(r\) 無法回到 \(l\) 當且僅當存在一個位置 \(p\) 滿足 \(l \le p \le r\) 且 \(\mathrm{suf}'[r] > \mathrm{suf}'[p]\)。
令 \(\mathrm{minsuf}'(l, r)\) 表示 \(\displaystyle \min_{l \le i < r} \{ \mathrm{suf}'[i] \}\)(注意 \(i\) 的范圍是 \([l, r)\),是左閉右開區間)。
則還需要在 \(g_r\) 上增加的代價就為 \(\max \{0, \mathrm{suf}'[r] - \mathrm{minsuf}'(l, r) \}\)。
綜上所述,\(w(l, r)\) 應該等於 \(\mathrm{cost}(l, r) + \max \{0, \mathrm{suf}'[r] - \mathrm{minsuf}'(l, r) \}\)。
令 \(w'(l, r) = \mathrm{cost}(l, r) + \mathrm{suf}'[r] - \mathrm{minsuf}'(l, r)\),也就是去掉了和 \(0\) 取 \(\mathrm{max}\) 的操作。
那么要求的就是滿足 \(\mathrm{cost}(l, r) \le k\) 且 \(w'(l, r) \le k\) 的,最大的 \(r - l + 1\) 的值。
考慮計算某個左端點 \(l\) 的答案,可以記 \(S_l = \{\mathrm{next}[l], \mathrm{next}[\mathrm{next}[l]], \ldots \}\)。
可以發現這個是一個鏈狀結構,把所有的 \(l \to \mathrm{next}[l]\) 的邊畫出來后,其實是一個樹狀結構(准確地說是森林)。
建立一個虛根 \(\mathrm{root}\),所有的不存在 \(\mathrm{next}\) 的位置(也就是每個森林的根)連向 \(\mathrm{root}\)。
然后從 \(\mathrm{root}\) 開始 DFS,每次進入子樹的時候考慮增加一個 \(\mathrm{next}\) 的貢獻即可,也就是每次給 \(S_l\) 新添一個元素。
當左端點 \(l\) 固定時,需要找到的就是滿足 \(\mathrm{cost}(l, r) \le k\) 且 \(w'(l, r) \le k\) 的最大的 \(r\)。
注意到 \(\mathrm{cost}(l, r)\) 關於 \(r\) 是個遞增的函數,所以 \(\boldsymbol{r}\) 不能太大,先二分求出 \(\boldsymbol{r}\) 的上限。
而 \(w'(l, r)\) 有三部分,我們分別考慮:
對於 \(\mathrm{cost}(l, r)\),每一個 \(S_l\) 的元素 \(x\) 會對 \(\mathrm{cost}(l, r)\) 產生一個后綴加的貢獻(從 \(x\) 開始的后綴)。
對於 \(\mathrm{suf}'[r]\),每一個 \(S_l\) 的元素 \(x\) 會對 \(\mathrm{suf}'[r]\) 產生一個后綴減的貢獻(從 \(x\) 開始的后綴)。
很神奇的是:這兩個貢獻,作用的后綴完全相同,產生的貢獻互為相反數,那么我們完全不管也是可以的。
也就是說: \(\mathrm{cost}(l, r) + \mathrm{suf}'[r]\),這個值是固定不變的,它永遠等於 \(\mathrm{suf}[r]\)。
最后,對於 \(\mathrm{minsuf}'(l, r)\),每次對 \(\mathrm{suf}'\) 有一個后綴減的貢獻(從 \(\boldsymbol{x - 1}\) 開始的后綴)。
之前定義為左閉右開區間的優勢在這里顯現出來了:
對於 \(r \ge x\) 的情況,會給 \(g_{x - 1}\) 加上 \(\mathrm{val}\)(注意是 \(x - 1\)),這是不好處理的(如果 \(r = x - 1\) 比較難辦)。
但是定義為 \([l, r)\),那么 \(g_{x - 1}\) 的修改就會在 \(\mathrm{minsuf}'(l, x)\) 上才顯現出來,這就避免了 \(r = x - 1\) 時的問題。
那么也就是說,對於固定的 \(l\),每個位置 \(r\) 的代價為 \(\mathrm{suf}[r] - \mathrm{minsuf}'(l, r)\),考慮使用線段樹維護這個代價。
而 \(r\) 的取值范圍顯然為 \(l \le r \le r_{\max}\),其中 \(r_{\max}\) 為之前確定的上限。
注意到 \(\displaystyle \mathrm{minsuf}'(l, r) = \min_{l \le i < r} \{ \mathrm{suf}'[i] \}\),其中這個 \(l \le i\) 不好處理,因為要計算很多個 \(l\) 的答案。
考慮在處理某個特定的 \(l\) 時,把 \(\mathrm{suf}'[1] \sim \mathrm{suf}'[l - 1]\) 加上 \(\infty\),這樣就取消了 \(i < l\) 的影響。
這之后 \(\mathrm{minsuf}'(l, r)\) 就可以看作是 \(\displaystyle \min_{1 \le i < r} \{ \mathrm{suf}'[i] \}\) 了。
對於 \(r \le r_{\max}\) 的限制我們也如法炮制,把 \(\mathrm{suf}'[r_{\max}] \sim \mathrm{suf}'[n]\) 減去 \(\infty\),即可消除 \(r > r_{\max}\) 的影響。
(注意是包括 \(\mathrm{suf}'[r_{\max}]\),這樣才能把 \(\mathrm{minsuf}'(l, r_{\max} + 1)\) 給變成 \(-\infty\))
當然,對於 \(\boldsymbol{l \le r \le r_{\max}}\) 的處理,可以直接在線段樹上提取區間,但復雜度可能會退化為 \(\boldsymbol{\mathcal O (n \log^3 n)}\)。
(存在一種精細實現的方法使得復雜度不退化,但是需要先把所有區間提取出來,再從左到右掃一遍,從右到左查詢,最后可以得到時間復雜度為 \(\mathcal O (n \log^2 n)\),實現起來有點復雜)
所以最終就變成了一個線段樹維護 \(\displaystyle a_i - \min_{1 \le j < i} \{ b_j \}\) 的最小值的問題,然后對於修改需要支持 \(b_i\) 的區間加減法。
這個東西怎么維護呢?
考慮一個類似樓房重建的線段樹算法:
線段樹中的每個節點維護區間 \(\mathrm{suf}[i], \mathrm{suf}'[i]\) 的最小值,
再特別維護一個:僅考慮本區間的 \(\mathrm{suf}'[i]\) 時,\(r\) 的取值僅在右子樹的答案的最小值。
在 Pushup 的時候使用一個類似樓房重建的每次只遞歸到一個子樹內的函數維護當前節點信息。
在查詢答案時,使用一個類似線段樹上二分的結構求得最靠右的滿足 \(w'(l, r) \le k\) 的下標 \(r_{\mathrm{ans}}\)。
這部分與樓房重建相比,還是有很多新的東西,需要再詳細說一下:
當區間左側傳來的最小值 \(pre\) 比左子樹的 \(\mathrm{suf}'[i]\) 的最小值小時,也就是左子樹完全被 \(pre\) 控制,
也就是,左子樹中的條件變為 \(\mathrm{suf}[i] - pre \le k\),就是 \(\mathrm{suf}[i] \le k + pre\),這部分是正常的線段樹上二分。
但是不要忘記了還要遞歸進右子樹查詢,雖然兩邊子樹都遞歸了,但是時間復雜度沒有退化到 \(\mathcal O (n)\)。
因為每次遞歸到左子樹時,都是 \(\mathcal O (\log n)\) 的,而可能有 \(\mathcal O (\log n)\) 次向左子樹的遞歸,所以是 \(\mathcal O (\log^2 n)\) 的。
具體細節請見從《樓房重建》出發淺談一類使用線段樹維護前綴最大值的算法。
時間復雜度為 \(\mathcal O (n \log^2 n)\),評測鏈接。
cf578F
令矩陣中為 *
的格子的個數為 \(k\)。
\(n \times m\) 的網格,就有 \((n + 1) \times (m + 1)\) 個格點,我們把格點交錯黑白染色。
則可以發現,每個鏡子都是連接兩個同色格點。如果我們把每面鏡子看作一條邊,則合法的圖有什么性質呢?
可以發現,要么所有的黑點連成一棵樹,要么所有的白點連成一棵樹,分別對應同一條光線在射入的格子的順時針一格或者逆時針一格射出的情況。
而如果其中一種顏色連成了一棵樹,那么另一種顏色的連邊方案就被唯一確定了。
因為保證了 \(k \le 200\),所以使用並查集合並一些點后,剩余點數為 \(\mathcal O (n + m + k)\) 個。
再使用矩陣樹定理即可得到答案。
時間復雜度為 \(\mathcal O (n m \alpha(n m) + {(n + m + k)}^3)\),評測鏈接。
2020-03-09
cf708D
這是一個有源匯帶上下界最小費用可行流的問題,然而這個“帶上下界”其實是假的。
注意到,只要 \(f\) 在 \(0 \sim c\) 內,就可以用 \(1\) 的代價更改,但是一旦要超過 \(c\),就需要同時更改 \(c\),相當於代價為 \(2\)。
對 \(f \le c\) 的邊和 \(f > c\) 的邊分開考慮:
- 如果 \(f \le c\),則最終的 \(f'\) 可以減小或增大,故連如下三條邊:
- 減小 \(f\):也就是從 \(v\) 到 \(u\) 退流,連 \(v \to u\),容量為 \(f\),費用為 \(1\)。
- 增大 \(f\)(\(f \le c\) 的部分):連 \(u \to v\),容量為 \((c - f)\),費用為 \(1\)。
- 增大 \(f\)(\(f > c\) 的部分):連 \(u \to v\),容量為 \(\infty\),費用為 \(2\)。
- 如果 \(f > c\),則至少要花費 \((f - c)\) 的代價,且在 \(c \le f' \le f\) 時取到,故先貢獻 \((f - c)\),連如下三條邊:
- 減小 \(f\),最終的 \(f'\) 在 \(c \sim f\) 之間:連 \(v \to u\),容量為 \((f - c)\),費用為 \(0\)。
- 減小 \(f\),最終的 \(f' \le c\):連 \(v \to u\),容量為 \(c\),費用為 \(1\)。
- 增大 \(f\):連 \(u \to v\),容量為 \(\infty\),費用為 \(2\)。
然后對於初始的每條邊,連 \(u \to v\),容量下界和上界均為 \(f\),費用為 \(0\)。
實際上,因為本題中的上下界把“有源匯帶上下界可行流”轉化成普通最大流,就是:
新建超級源和超級匯 \(S, T\)。
如果 \(u\) 點流入比流出多,就連 \(S \to u\),容量為流入流出的差值,費用為 \(0\)。
如果 \(u\) 點流出比流入多,就連 \(u \to T\),容量為流出流入的差值,費用為 \(0\)。
最后,為了取消原來的源匯,新建一條 \(n \to 1\),容量為 \(\infty\),費用為 \(0\)。
然后跑一次超級源到超級匯的最小費用最大流,一定是滿流的,轉化回原圖就是一個最小費用可行流了。
如果使用 SPFA 輔助實現的類 Dinic 算法求最小費用最大流,時間復雜度為 \(\mathrm{MCMF}( \!\left< |V|, |E|, F \right>\! = \!\left< \mathcal O(n), \mathcal O(n + m), \mathcal O (mv) \right>) = \mathcal O (n (n + m) m v)\),其中 \(v\) 為值域,本題中為 \({10}^6\)。
時間復雜度為 \(\mathcal O (n (n + m) m v)\),評測鏈接。
cf538H
也就是要把老師分成兩組,滿足每組的 \([l_i, r_i]\) 的交集,“相加”后與 \([t, T]\) 有交。
如果有三個老師的 \([l_i, r_i]\) 兩兩無交集,那么就完蛋了,根本沒法分組。
否則,如果我們考慮其中一組的人數為 \(n_1 = \min \{ r_i \}\),另一組的人數為 \(n_2 = \max \{ l_i \}\)。
如果 \(n_1 \ge n_2\),也就是所有老師的 \([l_i, r_i]\) 兩兩有交的情況,那么這種情況下每個老師都可以任意選組。
如果 \(n_1 < n_2\),那么這也是最“松”的一種方案了,如果 \(n_1\) 增大或 \(n_2\) 減小都會導致某個老師無法選組的情況。
那么,也就是說 \(n_1\) 只能減小,\(n_2\) 只能增大。
然而,現在 \(n_1 + n_2\) 還不一定滿足在 \([t, T]\) 內的情況。
所以如果 \(n_1 + n_2 < t\),就只能增大 \(n_2\);如果 \(n_1 + n_2 > T\),就只能減小 \(n_1\)。
那么就可以算出最優的一個 \(n_1\) 和 \(n_2\) 的選取方案,再根據這個方案對老師進行一次二分圖染色判定即可。
時間復雜度為 \(\mathcal O (n + m)\),評測鏈接。
2020-03-10
cf704E
套路樹鏈剖分,然后觀察一條鏈的情況:
有一個數軸,上面會有點突然出現,然后移動,最后消失,問最早的兩點重合的時間。
把所有鏈的時間取 \(\min\) 即可得到答案。
對於一個數軸,我們把時間維也顯示出來,然后畫出每個點的“世界線”(物體在時空中的軌跡)。
可以發現,每個點的軌跡都是一條線段,只要算出在時間維度上的最早的交點即可。
如圖,這是樣例 #1 的情況,考慮的重鏈為 \(1 - 4 - 6 - 5 - 2\) 這條,答案為 \(27.3\)。
我們看一下交點附近的細節:
到底要怎么找到並確定這個交點的坐標呢?如果兩兩判斷那必然是不行的,而且交點確實也可能有 \(\mathcal O (m^2)\) 個。
從“找到最早的交點”出發,我們考慮按照時間從小到大做掃描線。
考慮這樣的一個“第一個交點”,我們可以發現,交出它的兩條線段,一定在之前的某一個時刻,是相鄰的。
也就是說,不可能之前的每個時刻都有至少一條線段擋在它們之間,考慮反證法顯然。
那么我們只需要動態維護在掃描線過程中的,相鄰線段的交就行了。
掃描線無非就是加入一條線段,刪除一條線段,也就是說只要維護加入和刪除的時候的旁邊兩條線段就行。
那么最終只有 \(\mathcal O (m)\) 對相鄰線段,這是很重要的一步轉化。
那么我們需要一個合適的數據結構來尋找相鄰線段並能夠維護加入刪除線段。
令人意想不到的是,我們的 set 居然就滿足條件。set 中存當前的線段,但是排序方式怎么辦呢?
我們使用一個全局變量 \(T\) 來控制線段在 \(t\) 時刻下的橫坐標位置,然后使用橫坐標位置排序即可。
因為只要沒有交點,線段之間的相對位置就不會改變,這是不會觸發 set 的未定義行為的。
當然,如果 \(T\) 已經大於等於當前求出的交點時刻,就要及時退出,不要調用 set,以免觸發 UB 導致不好的結果。
要注意的是,也不要一求出交點就退出,第一個求出的交點不一定是最早的交點。
時間復雜度為 \(\mathcal O (m \log m \log n)\),評測鏈接。
cf696F
最優情況下,一定是一個點控制凸多邊形的連續若干條邊,另一個點也是連續若干條邊。
那么我們考慮先二分答案,然后尺取法確定以每條邊為左端點時,右端點最多到哪里。
具體實現時用半平面交即可。
時間復雜度為 \(\mathcal O (n^2 (\log v - \log \varepsilon))\),其中 \(v\) 為值域,評測鏈接。
2020-03-11
cf666D
實際上是一個暴力憨批題,從它只有 \(50\) 組數據也看得出來。
每個點要么橫着動要么豎着動,枚舉一下。
然后畫出每個點可以到達的位置的軌跡,也就是四條線,但是可能重合。
有很多情況,可以發現只有這三種情況可能有解:
- 橫線豎線都 \(2\) 條,當且僅當它們的 \(4\) 個交點組成正方形時有解,然后再枚舉點對配對情況。
- 橫線 \(2\) 條豎線 \(1\) 條(或反過來),那么正方形的頂點一定是那兩個交點,然后再枚舉是往哪一邊。
- 橫線 \(2\) 條沒有豎線(或反過來),徒手解一下整數規划(大霧)就行。
然后就沒了。
時間復雜度為 \(\mathcal O (t)\),評測鏈接。
2020-03-12
cf516E
令 \(d = \gcd(m, n)\),則可以發現,每一天,只有編號對 \(d\) 取余相同的一對男女生會一起玩。
也就是說,編號模 \(d\) 不同余的兩人永遠不會產生直接或間接關系,可以分開考慮。
那么就分成了 \(d\) 個子問題,第 \(i\)(\(0 \le i < d\))個子問題包含所有 \((x \bmod d) =i\) 的編號為 \(x\) 的人。
算出第 \(i\) 個子問題的答案之后,乘以 \(d\) 再加上 \(i\) 就能得到原問題的這個部分的答案,所有答案取 \(\max\) 即可。
由裴蜀定理,只要子問題中有一個人是快樂的,那么最終這個子問題中的所有人都會變得快樂。
特別地,如果 \(d > g + b\),則因為至少存在一個子問題沒有快樂的人,所以原問題無解。
這樣就幫我們排除掉了 \(d\) 太大的情況,現在 \(1 \le d \le 2 \times {10}^5\)。
那么我們讓 \(m, n\) 都除掉 \(d\),只需要考慮這個子問題,注意此時 \(m, n\) 互質。
需要注意的是,如果所有人一開始就是快樂的(子問題可能存在此情況),請返回 \(-1\) 而不是 \(0\),要不然會出錯。
而如果所有人都不是快樂的,請返回無解。否則就考慮一般情況:
我們考慮單獨計算最后一個女生變快樂的天數和最后一個男生變快樂的天數,取 \(\max\) 得到答案。
假設最后一個變快樂的女生是 \(i\) 號,那么她一定是在第 \(x\) 天變快樂的,滿足 \((x \bmod m) = i\)。
假設是因為 \(j\) 號男生變快樂的,這里 \(j = (x \bmod n)\),那么就需要考慮 \(j\) 號男生是什么時候變快樂的。
如果 \(j\) 號男生一開始就是快樂的,那么顯然他會在第 \(j\) 天讓 \((j \bmod m)\) 號女生變快樂。
令 \(c = (x - j) / n\),也就是從他讓 \((j \bmod m)\) 號女生變快樂,直到他讓 \(i\) 號女生變快樂之間經過的輪數。
則我們考慮在第 \((j + n)\) 天,這個男生會讓 \(((j + n) \bmod m)\) 號女生變快樂。
一直到第 \((j + c n)\) 天,也就是第 \(x\) 天,他會讓 \(((j + c n) \bmod m)\) 號女生,也就是 \(i\) 號女生變快樂。
那么我們是不是可以說,是 \(\boldsymbol{(j \bmod m)}\) 號女生,在經過 \(\boldsymbol{c}\) 輪(\(\boldsymbol{c n}\) 天)之后,讓 \(\boldsymbol{((j + cn) \bmod m)}\) 號女生,也就是 \(\boldsymbol{i}\) 號女生變快樂的。
或者簡化地說,如果 \(\boldsymbol{k}\) 號女生變快樂了,那么在 \(\boldsymbol{n}\) 天之后,\(\boldsymbol{((k + n) \bmod m)}\) 號女生一定會變快樂。
反復利用這個簡化的結論 \(c\) 次,就可以推出 \(c n\) 天之后讓 \(((k + cn) \bmod m)\) 號女生變快樂的結論。
也就是說,對於每個女生 \(k\),連一條從 \(k\) 出發,到達 \(((k + n) \bmod m)\) 的邊,邊權為 \(n\)。
- 這表示 \(k\) 號女生在某一天變快樂了,那么 \(n\) 天后,\(((k + n) \bmod m)\) 號女生會變快樂。
然后,對於每個一開始就是快樂的男生 \(j\),從源點 \(S\) 連一條到達 \((j \bmod m)\) 的邊,邊權為 \(j\)。
- 這表示 \(j\) 號男生會在第 \(j\) 天,第一次讓 \((j \bmod m)\) 號女生變快樂,之后就是女生之間自己連邊的事情了。
最后,對於每個一開始就是快樂的女生 \(i\),從源點 \(S\) 連一條到達 \(i\) 的邊,邊權為 \(i\)。
- 這表示從第 \(i\) 天開始,這個女生就可以每 \(n\) 天讓另一個女生變快樂。
求出 \(S\) 到每個點的最短路,對於一個一開始不是快樂的女生,\(S\) 到她的最短距離就會對答案產生貢獻。
注意,對於一個一開始就是快樂的女生,不產生貢獻,因為她並不是在第 \(i\) 輪才變快樂的,而是從一而終都快樂。
但是總點數是 \({10}^9\) 級別的,沒法做。
注意到第一類邊很有特點,把所有女生連成了一個大環。
但是環上的點是每次 \(x \to ((x + n) \bmod m)\) 的,不按順序。
我們用某種方式把這些點重標號,然后注意到只需要考慮二三類邊中,與 \(S\) 相連的關鍵點即可。
把這些關鍵點按照在環上的順序排序,就可以快速處理最短路了。
以上是對女生的考慮,如果是男生同理,只要轉換一下就行。
時間復雜度為 \(\mathcal O ((b + g) \log (b + g))\),評測鏈接。
agc027_f
首先我們看看這兩棵樹是否完全一樣,是的話輸出 \(0\) 然后走人。
否則我們枚舉第一次操作:選了某個葉子 \(u\),然后把它接到 \(v\) 上(\(v\) 不一定是 \(u\) 原來連接的點)。
那么我們發現,\(u\) 不能再被選擇了,那么考慮在兩棵樹(注意這里是新的 \(A\))中都把 \(u\) 當作根。
把“新的 \(A\)”和 \(B\) 以 \(u\) 為根拎起來后,就很容易看出,因為樹根不能動,所以操作都是從葉子開始。
也就是說:如果不去操作一個點 \(x\) 的話,那么 \(x\) 的雙親節點將永遠不會改變。
但是如果操作了一個點的話,又把它接回原來的位置顯然不優,所以可以得出結論:
- 一個點被操作,當且僅當它在兩棵樹中的雙親節點不同。
如果一個點不被操作,但是它的雙親節點要被操作,這是不可能的,因為它的雙親節點沒有成為葉子的機會。
這是一個判無解的依據。
其次我們可以發現,在“新的 \(A\)”中,一個點一定比它的雙親節點更早被操作(假設它們都需要被操作)。
類似地,在 \(B\) 中,一個點一定比它的雙親節點更晚被操作。
那么我們把這個關系連邊,如果可以成功拓撲排序,則自然有解,且拓撲排序的過程就構造了一個合法方案。
那么每次建圖並跑拓撲排序的時間復雜度為 \(\mathcal O (N)\)。
時間復雜度為 \(\mathcal O (T N^3)\),評測鏈接。
2020-03-13
agc024_f
我們直接考慮所有 \((2^{N + 1} - 1)\) 個長度在 \(0 \sim N\) 之間的 \(01\) 串,計算它們到底是多少個 \(S\) 中的串的子序列。
當枚舉的串為 \(A\) 時,令這個值為 \(\mathrm{val}(A)\),這樣直接枚舉所有 \(01\) 串就能得到答案了。
我們考慮子序列自動機,也就是在判定一個串 \(A\) 是不是另一個串 \(B\) 的子序列時:
比如 \(A = \mathtt{011001}\),\(B = \mathtt{110110101}\),因為 \(A\) 中的第一個字符是 \(\texttt{0}\),考慮 \(B\) 中第一個 \(\texttt{0}\) 的位置。
用 \(A\) 的第一個字符 \(\texttt{0}\) 去匹配 \(B\) 中的第一個 \(\texttt{0}\),則轉化為 \(A = \mathtt{11001}\),\(B = \mathtt{110101}\)。
所謂“子序列自動機”的好處在於它的匹配過程是唯一的。
那么,我們這樣考慮:對於一個 \(01\) 串 \(C \in S\),我們希望 \(C\) 的所有子序列 \(A\) 的 \(\mathrm{val}\) 值都加上 \(1\)。
那么考慮 DP:令 \(\mathrm{dp}(A | B)\) 表示使用 \(A\) 去匹配某個原串 \(C \in S\),匹配剩下的串是 \(B\),原串 \(C\) 的方案數。
比如 \(\mathrm{dp}(\mathtt{00} | \mathtt{110110101})\) 可以轉移到:
- 匹配了第一個 \(\mathtt{0}\):\(\mathrm{dp}(\mathtt{000} | \mathtt{110101})\)。
- 匹配了第一個 \(\mathtt{1}\):\(\mathrm{dp}(\mathtt{001} | \mathtt{10110101})\)。
- 停止匹配:\(\mathrm{dp}(\mathtt{00} | \varepsilon)\)。
則可以發現,\(\mathrm{val}(A) = \mathrm{dp}(A | \varepsilon)\)。關於邊界條件,令 \(\mathrm{dp}(\varepsilon | C) = 1\) 即可(\(C \in S\))。
這樣 DP 的話,因為過程中 \(|A| + |B| \le N\),所以狀態總數是 \(\mathcal O (N 2^N)\) 的。
使用合適的方法存儲狀態,就可以做到 \(\mathcal O (N 2^N)\) 的復雜度。
時間復雜度為 \(\mathcal O (N 2^N)\),評測鏈接。
2020-03-14
agc027_e
我們考慮判斷一個字符串 \(t\) 能否通過 \(s\) 變換得到。
假設可以,則對於 \(t\) 的每一個字符,都對應了 \(s\) 中的一段連續區間。
也就是,\(s\) 的一個子串,經過若干次操作后,變成了單一的一個字符 \(\mathtt{a}\) 或 \(\mathtt{b}\)。
這里有個很有趣的結論:如果我們把 \(\mathtt{a}, \mathtt{b}\) 分別看作 \(1, 2\),則執行操作是不會改變所有字符之和模 \(3\) 的結果的。
也就是說,定義 \(p(s)\) 為字符串 \(s\) 中所有字符之和模 \(3\),則 \(s\) 經過若干次操作變成 \(t\),\(p(s)\) 與 \(p(t)\) 是相等的。
那么回到變成字符的問題上來,如何判斷 \(s\) 能不能變成單一的字符 \(\mathtt{a}\) 或 \(\mathtt{b}\) 呢?
顯然 \(p(s)\) 應該等於 \(1\) 或者 \(2\),這對應了最終變成的是 \(\mathtt{a}\) 或者是 \(\mathtt{b}\),顯然這是必要條件。
但是這並不是充分條件,考慮 \(s = \mathtt{ababa}\),雖然 \(p(s) = p(\mathtt{a})\),但是根本沒法對 \(s\) 進行操作,所以不可行。
如果 \(s\) 中存在兩個相鄰的相同字符呢?事實證明,再加上這個條件就是充分的了。
考慮歸納:當 \(|s| \le 2\) 時成立;否則只要對一個和異種字符相鄰的位置進行操作,就能使長度變小 \(1\) 且還滿足條件。
也就是說,字符串 \(\boldsymbol{s}\) 能變成單個字符 \(\boldsymbol{c}\) 當且僅當 \(\boldsymbol{p(s) = p(c)}\) 且「\(\boldsymbol{|s| = 1}\) 或 \(\boldsymbol{s}\) 中存在相鄰的相同字符」。
那么再回到一開始的問題:字符串 \(t\) 能否通過 \(s\) 變換得到?
很自然地,有一個貪心匹配的算法:
依次考慮 \(t\) 中的每一個字符,選擇一個 \(s\) 的最短前綴變換成這個字符,然后刪掉這個前綴繼續匹配。
舉個例子,當 \(t = \mathtt{abba}\) 且 \(s = \mathtt{aabbabaabaaabab}\) 時:
- \(t_1 = \mathtt{{\color{red}{a}}}\),選取 \(s\) 的滿足條件的最短前綴:\(s = \mathtt{{\color{red}{a}}abbabaabaaabab}\)。
- \(t_2 = \mathtt{{\color{blue}{b}}}\),選取 \(s\) 的滿足條件的最短前綴:\(s = \mathtt{{\color{red}{a}}{\color{blue}{abb}}abaabaaabab}\)。
- \(t_3 = \mathtt{{\color{purple}{b}}}\),選取 \(s\) 的滿足條件的最短前綴:\(s = \mathtt{{\color{red}{a}}{\color{blue}{abb}}{\color{purple}{abaa}}baaabab}\)。
- \(t_4 = \mathtt{{\color{green}{a}}}\),選取 \(s\) 的滿足條件的最短前綴:\(s = \mathtt{{\color{red}{a}}{\color{blue}{abb}}{\color{purple}{abaa}}{\color{green}{baa}}abab}\)。
最后 \(s = \mathtt{{\color{red}{a}}|{\color{blue}{abb}}|{\color{purple}{abaa}}|{\color{green}{baa}}|abab}\),被分成了五段(前 \(|t|\) 段加上一個未匹配的后綴)。
特別地,這個最后一段需要滿足 \(\boldsymbol{p}\) 值為 \(\boldsymbol{0}\),例如這里 \(p(\mathtt{abab}) = 0\)(因為需要滿足 \(p(s) = p(t)\))。
我們可以證明:\(s\) 能變成 \(t\) 當且僅當能夠成功分段,且 \(s\) 存在兩個相鄰的相同字符(或 \(t = s\))。
注意:證明部分可以酌情跳過!
注意:證明部分可以酌情跳過!
注意這里包含了兩個條件,第一個是成功分段,第二個是 \(s\) 存在相鄰的相同字符。
我們先考慮 \(s\) 不存在相鄰的相同字符的情況,顯然輸出 \(1\) 即可(當 \(t = s\) 時)。
那么在后續證明中假定 \(s\) 存在相鄰的相同字符,條件變為:能夠成功分段。
我們首先證明它的必要性,即任意一個合法的 \(t\) 都能夠成功分段:
- 對於某個 \(t\),我們考慮它在 \(s\) 中對應的每個段。
- 即 \(s = s_1 \, s_2 \, \cdots \, s_{|t|}\),這里每個 \(s_i\) 都是一個字符串,且 \(s_i\) 變成了 \(t\) 的第 \(i\) 個字符。
- 假設 \(t_i\) 對應的 \(s_i\) 是第一個非最短前綴(也就是說之前的字符對應的前綴都是最短的)。
- 令此時的最短前綴為 \(x\),則 \(s_i = x \, y\),其中 \(p(y) = 0\)。
- 因為 \(p(y) = 0\),考慮把 \(y\) 並入后一個 \(s_{i + 1}\),以保證 \(s_i\) 的最短性。
- 如果 \(s_{i + 1}\) 不存在,也就是 \(i = |t|\),則結論自然成立(\(y\) 成為最后一段未匹配后綴)。
- 否則當前的 \(s_{i + 1} \gets y \, s_{i + 1}\),此時至少需要保證 \(s_{i + 1}\) 是合法的,如果合法就可以繼續遞歸。
- 但是問題在於可能不合法,可以發現此時 \(|s_{i + 1}| \ge 2\),那么就是 \(s_{i + 1}\) 中不存在相鄰的相同字符。
- 也就是形如 \(y = \mathtt{baba}\),而原先的 \(s_{i + 1} = \mathtt{b}\) 的這種形式,新的 \(s_{i + 1} = \mathtt{babab}\)。
- 那么我們注意到,如果把 \(s_{i + 1}\) 拆分為 \(\mathtt{b|abab}\) 兩段,然后把 \(\mathtt{abab}\) 繼續並入后面進行討論。
- 這樣就可以保證此時的 \(s_{i + 1} = \mathtt{b}\),顯然是最短的,然后把問題轉移到后面了,遞歸即可。
- 所以我們證明了:任意一個合法的 \(t\) 都能夠成功分段。
還需要證明它的充分性,即只要能夠成功分段,那就一定是合法的:
- 考慮最終分段的情況,假如不存在最后一段未匹配后綴,則自然成立,否則:
- 假設 \(s = s_1 \, s_2 \, \cdots \, s_{|t|} \, s_{|t| + 1}\),其中 \(s_{|t| + 1}\) 是未匹配后綴,我們要想辦法把這個后綴塞到前面去。
- 首先直接把 \(s_{|t|}\) 與 \(s_{|t| + 1}\) 合並,如果可行就合法,不過如果不可行呢?
- 類似地,自然有形如 \(s_{|t|} = \mathtt{a}\) 和 \(s_{|t| + 1} = \mathtt{baba}\) 這種形式。
- 那么我們令 \(s_{|t|} = \mathtt{a}\),然后把 \(\mathtt{abab}\) 往前轉移。
- 因為有「\(s\) 中存在相鄰的相同字符」這個條件,這個過程一定會因為合並出了相鄰的相同字符而停止。
- 所以我們證明了:任意一個能夠成功分段的 \(t\) 都是合法的。
證明部分結束。
證明部分結束。
那么我們先特判 \(s\) 是 \(\mathtt{ababababa}\) 這種形式,直接輸出 \(1\),否則考慮做一個這樣的 DP:
令 \(dp(i)\) 表示考慮了 \(s\) 從 \(\boldsymbol{i}\) 開始的后綴,能夠成功分段的 \(t\) 的數量(注意最后未匹配的后綴的 \(p = 0\) 的限制)。
則答案就是 \(dp(1)\),轉移是比較顯然的。
我代碼中實現的時候,為了方便,把 \(t\) 是空串的情況也計入答案,輸出的時候要扣除。
時間復雜度為 \(\mathcal O (|s|)\),評測鏈接。
2020-03-16
cf526G
可以證明:使用 \(k\) 條路徑就可以覆蓋一棵有 \(2 k\) 的葉子的樹。
先以任意方式匹配葉子。如果有兩條路徑不相交,可以調整成相交的情況。
不斷調整就可以讓任意兩條路徑都相交,於是顯然覆蓋了整棵樹。
(證明不嚴謹,因為沒有說明調整能在有限步內結束,不過這不重要)
所以當詢問 \(y\) 的時候,就是要在原樹中選取不超過 \(2 y\) 個葉子,讓這些葉子組成的極小連通塊的邊權和盡量大。
再考慮:每次詢問中,一定存在一種方案使得直徑的兩端中至少有一端被選取。
那么我們以兩個直徑端點為根,每次詢問在兩棵樹中分別查詢即可。
那么,現在根是一個葉子(直徑端點必然是葉子),且根必選。
也就是說,需要選其它至多 \(2 y - 1\) 個葉子,打通他們到根的鏈,並且最大化邊權和。
考慮帶邊權的長鏈剖分,發現這和選取過程是等價的,也就是貪心地選取前 \(2 y - 1\) 個最長鏈即可。
但是選完之后不一定經過 \(x\),所以需要做一下調整。
首先打通 \(x\) 子樹中最深的點到根的路徑,然后需要去掉另一個葉子,使得減小量盡量小。
可以發現,要不然是刪掉第 \(2 y - 1\) 個最長鏈,也就是僅選取前 \(2 y - 2\) 個最長鏈,然后把 \(x\) 接上。
要不然就是在選取前 \(2 y - 1\) 個最長鏈的基礎上,先把 \(x\) 接上,然后刪去第一個碰到的點的其它子樹。
最優解,一定符合這兩種情況之一,且不會統計到錯誤的情況(葉子數都不超過 \(2 y - 1\)),所以是正確的。
時間復雜度為 \(\mathcal O ((n + q) \log n)\),評測鏈接。
2020-03-17
cf700E
后綴數組再次證明了:SAM 能做的題,SA 也能做。
我們嘗試改變一下定義,強制讓 \(t_{i + 1}\) 是 \(t_i\) 的 border,如果不是的話可以減小 \(t_i\) 的長度,顯然不更劣。
在這樣的調整下,我們發現可以這樣定義:
令 \(\mathrm{cool}(s)\) 表示強制要求 \(t_1 = s\) 以及 \(t_{i + 1}\) 是 \(t_i\) 的 border 的,最大的 \(k\) 值。
那么要求的即是 \(s\) 的所有子串中的最大的 \(\mathrm{cool}\) 值。
因為我們考慮使用 SA,所以令 \(\mathrm{f}[i]\) 表示 \(s\) 以 \(i\) 開始的后綴的所有前綴中的最大的 \(\mathrm{cool}\) 值。
然后我們考慮轉移,顯然從后往前轉移(后綴的長度遞增)是比較合理的。
假設我們要利用后面的信息求出 \(\mathrm{f}[i] = k\),根據定義,要么 \(\mathrm{f}[i] = 1\),要么有 \(t_2\) 是 \(t_1\) 的 border。
而如果 \(t_2\) 是 \(t_1\) 的 border,顯然 \(t_2\) 不僅是 \(s[i : n]\) 的一個前綴,而且還必須在 \(s[i + 1 : n]\) 中出現過至少 \(1\) 次。
而且我們還有 \(\mathrm{cool}(t_2) = k - 1\)。
那么我們考慮后面的所有 \(\mathrm{f}[j] = k - 1\) 的位置,它們就有可能轉移給 \(\mathrm{f}[i]\),然而實際上我們並不知道 \(k\) 的值。
也就是說,與其考慮一個位置從后轉移而來,不如換個角度,考慮讓一個位置向前轉移。
但是轉移的過程卻和后綴的 LCP 以及 \(\mathrm{f}[i]\) 對應的前綴長度息息相關:
一個 \(i\) 能轉移到更前面的 \(j\) 當且僅當 \(\mathrm{LCP}(j, i) \ge |s_i|\),其中 \(s_i\) 即為 \(\mathrm{cool}(s_i) = \mathrm{f}[i]\) 的一個 \(s[i : n]\) 的前綴。
可以發現,當有多個滿足條件的 \(\boldsymbol{s_i}\) 時,取最短的 \(\boldsymbol{s_i}\) 才是正確的。
這提示我們可能需要再維護一個對應的 \(s_i\) 長度的信息,記作 \(\mathrm{len}\)。如果 \(\mathrm{f}[i] = 1\),顯然 \(\mathrm{len}[i] = 1\)。
這時你可能會想出這樣的轉移過程:
如果 \(\mathrm{LCP}(j, i) \ge \mathrm{len}[i]\),那么 \(\mathrm{f}[j] = \mathrm{f[i]}\),\(\mathrm{len}[j] = \mathrm{len[i]} + i - j\)。
(當然要在以 \(\mathrm{f}\) 盡量大的前提下,讓 \(\mathrm{len}\) 盡量小)
但是很可惜這樣是錯誤的,考慮 \(s = \mathtt{acacaba}\)。
對於位置 \(7\) 有 \(\langle \mathrm{f}, \mathrm{len} \rangle = \langle 1, 1 \rangle\),轉移給位置 \(1, 3, 5\)。
對於位置 \(5\) 有 \(\langle \mathrm{f}, \mathrm{len} \rangle = \langle 2, 3 \rangle\),注意它無法轉移給 \(1\) 和 \(3\)。
但是,對於位置 \(3\),真實的 \(\langle \mathrm{f}, \mathrm{len} \rangle\) 應該為 \(\langle 2, 3 \rangle\),然而在這里變成了 \(\langle 2, 5 \rangle\),是被位置 \(7\) 而非位置 \(5\) 轉移的。
進而影響到位置 \(1\) 的值,最終影響答案,正確答案為 \(3\) 但是這樣求出來的答案為 \(2\)。
如果位置 \(5\) 當 \(\mathrm{f} = 1\) 時的 \(\mathrm{len}\) 也能轉移給位置 \(3\) 就好了,但是這是不現實的,因為 \(\mathrm{f}\) 可能太大無法一一轉移。
其實正確方法是,不用同時維護 \(\mathrm{len}\) 了,直接在使用時計算 \(\mathrm{len}\) 即可。
因為實際上,如果 \(\mathrm{len}\) 的計算正確,是不會影響 \(\mathrm{f}\) 值的轉移的,但是問題出在 \(\mathrm{len}\) 的計算上。
如果我們已知 \(\mathrm{f}[i] = k\),能否同時確定 \(\mathrm{len}\) 的值呢?
- 如果 \(k = 1\),則 \(\mathrm{len}[i] = 1\)。
- 否則我們還需要知道 \(\mathrm{f}[i]\) 是從哪個 \(\mathrm{f}[j] = k - 1\) 處轉移而來的。
- 如果有多處,應該選擇 \(\mathrm{len}[j]\) 最小的那一處。
- 獲得了 \(\mathrm{len}[j]\) 之后,我們在 SA 中定位 \(\mathrm{LCP}(i, x) \ge \mathrm{len}[j]\) 的可行的 \(x\) 的區間。
- 在線段樹中查詢這個區間內,在 \(i\) 之后的,且離 \(i\) 盡量近的位置 \(y\)。
- 則 \(\mathrm{len}[i] = \mathrm{len}[j] + y - i\)。
確定了 \(\mathrm{len}[i]\) 之后,就可以往前轉移了。在 SA 中定位 \(\mathrm{LCP}(x, i) \ge \mathrm{len}[i]\) 的可行的 \(x\) 的區間。
然后用線段樹,轉移給區間內的所有在 \(i\) 之前的位置,線段樹要維護在 \(k\) 盡量大的前提下讓 \(\mathrm{len}\) 盡量小。
時間復雜度為 \(\mathcal O (n \log n)\),評測鏈接。
2020-03-18
agc030_c
注意到 \(N\) 的上界是 \(K / 2\),那么我們考慮在大小約為 \(K / 2\) 的矩陣中構造方案。
先不考慮限制,注意到這樣的矩陣滿足條件:\(\begin{bmatrix} 1 & 1 & \cdots & 1 \\ 2 & 2 & \cdots & 2 \\ \vdots & \vdots & \ddots & \vdots \\ K & K & \cdots & K \end{bmatrix}\)。
但是沒法變成 \(K / 2\) 的矩陣。
我們轉一轉,發現這樣的矩陣也滿足條件:\(\begin{bmatrix} 1 & 2 & 3 & \cdots & K \\ 2 & 3 & 4 & \cdots & 1 \\ 3 & 4 & 5 & \cdots & 2 \\ \vdots & \vdots & \vdots & \ddots & \vdots \\ K & 1 & 2 & \cdots & K - 1 \end{bmatrix}\)。
但是它還是 \(K \times K\) 的,不過我們可以考慮在里面塞進去一些新值。
比如,當 \(K = 4\) 的時候我們可以把一些 \(1\) 替換成 \(5\):\(\begin{bmatrix} {\color{red}{1}} & 2 & 3 & 4 \\ 2 & 3 & 4 & {\color{blue}{5}} \\ 3 & 4 & {\color{red}{1}} & 2 \\ 4 & {\color{blue}{5}} & 2 & 3 \end{bmatrix}\)。
可以發現,因為用了對角線填充,所以仍然滿足條件,十分巧妙。
同理把一些 \(2\) 換成 \(6\) 也是可以的,所以 \(N \times N\) 的矩陣可以填進 \(2 N\) 種數(前提是 \(N\) 是偶數)。
所以找到合適大小的偶數 \(N\),滿足 \(N \le K \le 2 N\) 就行了。
時間復雜度為 \(\mathcal O (K^2)\),評測鏈接。
2020-05-06
agc038_e
詳細題解請見:AtCoder Grand Contest 038 E: Gachapon,評測鏈接。