P9746 「KDOI-06-S」合併序列
經典區間dp+預處理
不難設計狀態 \(f_{l,r}\) 表示 \([l,r]\) 能否變為一個數,轉移也簡單,列舉三個區間,滿足 \(f_{i,a}=f_{b,c}=f_{d,r}=1\) 且異或和為 \(0\)。複雜度為 \(O(n^6)\)。
設異或和為 \(s_{l,r}\)。
考慮最佳化,瓶頸在於轉移需要列舉三個區間。假如只列舉一個右區間 \([d,r]\),那麼我們只需要判斷 \([l,d-1]\) 中是否存在兩個區間能縮成一個數且兩區間異或和為 \(s_{d,r}\) 就可以轉移。
直接設 \(h_{i,j}\) 表示滿足 \(i\le a\le b\le c\) 且 \(s_{i,a}\oplus s_{b,c}=j\) 的最小 \(c\) 值。但發現還是不好預處理出來,不好找到 \([b,c]\) 區間。再設 \(g_{i,j}\) 表示滿足 \(i\le a\le b\) 且 \(s_{a,b}=j\) 且 \(f_{a,b}=1\) 的最小 \(c\) 值,\(g\) 容易轉移,而 \(h\) 只需要列舉 \(a\) 即可,\(h_{i,s_{i,a}\oplus j}=\min g_{a+1,j}\ (f_{i,a}=1)\)。
\(f\) 轉移變為存在 \(l\le d\le r\),滿足 \(h_{i,s_{d,r}}<d\) 。單次複雜度為 \(O(n^2A)\),注意當前 \(f_{l,r}\) 為 \(1\) 後可以直接 break。
\(h\) 陣列和 \(g\) 陣列的轉移在區間 dp 時更新即可。值得注意的是,在這題中,區間 dp 的順序只能是左端點 \(l\) 從右向左列舉,右端點 \(r\) 從 \(l\) 向 \(n\) 列舉,因為 \(h\) 陣列和 \(g\) 陣列的更新需要之前所有長度的 \(f_{l,r}\),如果只按長度更新會出現錯誤,如列舉 \(l=a=1\) 時,若按長度列舉,無法轉移到 \(g_{a+1,j}\) 長度大於 \(1\) 時的區間。
對於輸出方案,除了記錄每個區間的前繼,還需要記錄 \(h\) 和 \(g\) 陣列轉移時的其中一個合法方案,對於編號的改變,這個體現在 dfs 中。這個輸出方案還是比較重要的。
void print(int l, int r, int id) {
if(l == r || l == 0) return;
int d = pre[l][r], a = hl[l][sum[r] ^ sum[d - 1]];
int now = sum[a] ^ sum[l - 1] ^ sum[r] ^ sum[d - 1];
int b = gl[a + 1][now], c = g[a + 1][now];
print(d, r, id + (d - l)), print(b, c, id + (b - l)), print(l, a, id);
ans.push_back({id, id + (b - l) - (a - l), id + (d - l) - (a - l) - (c - b)});
}
對於 \(h\) 和 \(g\) 出現的動機還是挺明確的,一個是最佳化需要,一個注意到 \(a_i\) 的大小,就知道可以把它放進狀態裡了。轉移需要注意。