題解-[POI2014]PRZ-Criminals
阿新 • • 發佈:2021-10-22
一道看起來蠻套路的題。首先如果確定了兩端的起點的話這題就非常好做了。
貪心地來想,每次走到第一個該走的顏色。舉個栗子,假設資料是這樣的:
10 5
1 2 3 2 4 3 5 2 2 1
3 2
2 3 4
2 4
那麼如果第一個人的位置是1
,下一個他會到位置2
的 \(2\),然後再到位置3
的 \(3\),再到位置5
的 \(4\)。可以證明這樣一定是最優的,選越前面的後面更有機會被選到。同理從後往前的那個人也是如此。於是這個問題可以 \(O(n)\) 解決。
但是這道題起點不固定,那麼我們只好列舉起點,注意到起點的顏色必須相同,那我們列舉顏色就好了。同上面的貪心,兩個點應越靠近兩邊越好。可是這樣子時間複雜度變為 \(O(nk)\)
這可以通過從後往前統計每種顏色最前的位置來實現。
統計好了之後暴力跳的複雜度還是 \(O(nk)\),但是我們可以路徑壓縮,類似並查集的複雜度證明可以知道這樣是均攤 \(O(k\log{n})\) 的。
因為這題卡空間所以我把一個數組用在了很多地方來節省空間。。。
#include <bits/stdc++.h> using namespace std; template <typename T> void read(T &X) { T f = 1; char ch = getchar(); for (; '0' > ch || ch > '9'; ch = getchar()) if (ch == '-') f = -1; for (X = 0; '0' <= ch && ch <= '9'; ch = getchar()) X = X * 10 + ch - '0'; X *= f; } int n, k; int c[1000001]; int m, l; int x[1000001], y[1000001]; int ans, tmp; int nxt1[1000001], nxt2[1000001]; int fa1[1000001], fa2[1000001]; int tail[1000001]; int Find1(int X) { if (fa1[X] == -1) return -1; return fa1[X] == n + 1 ? X : fa1[X] = Find1(fa1[X]); } int Find2(int X) { if (fa2[X] == -1) return -1; return fa2[X] == 0 ? X : fa2[X] = Find2(fa2[X]); } void work1() { int lst = n + 1; for (int i = n; i >= 1; i--) { nxt1[i] = lst; if (c[i] == x[1]) { lst = i; } } } void work2() { int lst = 0; for (int i = 1; i <= n; i++) { nxt2[i] = lst; if (c[i] == y[1]) { lst = i; } } } void work3() { for (int i = 1; i <= k; i++) tail[i] = -1; for (int i = n; i; i--) { if (nxt1[c[i]]) fa1[i] = tail[nxt1[c[i]]]; if (c[i] == tmp) fa1[i] = n + 1; tail[c[i]] = i; } } void work4() { for (int i = 1; i <= k; i++) tail[i] = -1; for (int i = 1; i <= n; i++) { if (nxt2[c[i]]) fa2[i] = tail[nxt2[c[i]]]; if (c[i] == tmp) fa2[i] = 0; tail[c[i]] = i; } } int main() { memset(fa1, -1, sizeof(fa1)); memset(fa2, -1, sizeof(fa2)); read(n); read(k); for (int i = 1; i <= n; i++) read(c[i]); read(m); read(l); for (int i = 1; i <= m; i++) read(x[i]); for (int i = 1; i < m; i++) nxt1[x[i]] = x[i + 1]; tmp = x[m]; for (int i = 1; i <= l; i++) read(y[i]); for (int i = 1; i < l; i++) nxt2[y[i]] = y[i + 1]; work3();//把所有 x_i 後的最近的 x_{i+1} 找出來 work4();//把所有 y_i 前的最近的 y_{i+1} 找出來 work1();//把所有位置後的最近的 x_1 找出來 work2();//把所有位置前的最近的 y_1 找出來 for (int i = 1; i <= k; i++) x[i] = 0; for (int i = 1; i <= n; i++) if (!x[c[i]]) x[c[i]] = i; for (int i = 1; i <= k; i++) tail[i] = 0; for (int i = n; i >= 1; i--) if (!tail[c[i]]) tail[c[i]] = i; for (int i = 1; i <= n; i++) y[i] = 0; for (int i = 1; i <= k; i++) {//列舉開始顏色 if (!x[i]) continue; if (nxt1[x[i]] == n + 1 || nxt2[tail[i]] == 0) continue; int fir = Find1(nxt1[x[i]]), sec = Find2(nxt2[tail[i]]); if (fir == -1 || sec == -1) continue; if (fir <= sec) { y[fir]++; y[sec + 1]--; } } for (int i = 1; i <= n; i++) { y[i] += y[i - 1]; if (y[i] > 0) { if (c[i] == tmp) { ans++; } } } printf("%d\n", ans); for (int i = 1; i <= n; i++) if (y[i] > 0) if (c[i] == tmp) printf("%d ", i); return 0; }