【題解】CSES 1076. Sliding Median === ###### tags: `競程` `題解` `資料結構` 參考資料: - [ref1](https://www.luogu.com.cn/blog/Chanis/gnu-pbds) - [ref2](https://usaco.guide/problems/cses-1076-sliding-median/solution) --- ## 作法一:兩個 multiset 以兩個動態排序的資料結構 $L$ $R$,分別維護前半小、後半大的,查詢時輸出中間切開的地方就行。 仔細地說,有奇數個元素時,中位數是 $\lceil \frac n 2 \rceil$,偶數個時,中位數是 $\frac n 2$。 使 $L$ 在 $n$ 為奇數時,比 $R$ 多一個,查詢時輸出 $L$ 的最後一個元素。 :::danger 這裏我踩到一個坑,就是 S.erase(x) 會把所有 x 刪除掉。 若只要刪除一個 x,使用 erase(S.lower_bound(x)) ::: ```cpp int n, k, a[N]; multiset<int> L, R; void era (int x) { if (L.find(x) != L.end()) L.erase(L.lower_bound(x)); else R.erase(R.lower_bound(x)); } void ins (int x) { if (L.empty() || x<=*last(L)) L.insert(x); else R.insert(x); } void bal () { while (R.size()>L.size()) { L.insert(*R.begin()); R.erase(R.begin()); } while (L.size()-1>R.size()) { R.insert(*last(L)); L.erase(last(L)); } } signed main() { cin >> n >> k; rep(i,1,n) cin >> a[i]; rep(i,1,n) { ins(a[i]); if (i>k) era(a[i-k]); bal(); if (i>=k) cout << *last(L) << "\n "[i+1<=n]; } } ``` ## 作法二:在值域線段樹上二分搜 - 找到最小的 $x$ 使 $cnt[1]+ \dots +cnt[x] \le \lceil \frac n 2 \rceil$ 以 $cnt[x]$ 代表目前 $x$ 出現幾次。 當 $n$ 為奇數,取 $n/2+1$。當 $n$ 為偶數,取第 $n/2$ 個,因此找到第一個包含 $\lceil \frac n 2 \rceil$ 的 $cnt[1]+ \dots + cnt[x]$ - 在線段樹上二分搜:$query(u,k)$ 所代表區間 $[l,r]$ 中,最左的 $i$ 使得前綴至少為 $k$ ``` if (sum[l[u]] >= k) return query(l[u],k) else return query(r[u],k-sum[l[u]]) ``` - 離散化 因為值域達 $10^9$,並且只關心數字間大小關係,所以離散化。 ```cpp int n, k, a[N]; int v[N], m; void lisan() { rep(i,1,n) v[i] = a[i]; sort(v+1,v+1+n); m = unique(v+1,v+1+n)-v-1; rep(i,1,n) a[i] = lower_bound(v+1,v+1+m,a[i])-v; } struct Seg { int tr[N<<2]; void modify (int p, int x, int u=1, int l=1, int r=m) { if (l==p && p==r) { tr[u] += x; return; } int mid = (l+r)/2; if (p<=mid) modify(p,x,u*2,l,mid); else modify(p,x,u*2+1,mid+1,r); tr[u] = tr[u*2] + tr[u*2+1]; } int query (int x, int u=1, int l=1, int r=m) { if (l == r) return l; int mid = (l+r)/2; if (tr[u*2] >= x) return query(x,u*2,l,mid); else return query(x-tr[u*2],u*2+1,mid+1,r); } } seg; signed main() { cin >> n >> k; rep(i,1,n) cin >> a[i]; lisan(); rep(i,1,n) { seg.modify(a[i],1); if (i > k) seg.modify(a[i-k],-1); if (i>=k) cout << v[seg.query((k-1)/2+1)] << "\n "[i+1<=n]; } } ``` ## 作法三:支援 find_by_order 的資料結構 可以自己刻一顆 treap,或是使用 pbds。 食用 pbds 前: ```cpp #include <ext/extc++.h> using namespace __gnu_pbds; ``` 然後快樂的 AC 他: ```cpp typedef tree< pii, null_type, less<pii>, rb_tree_tag, tree_order_statistics_node_update > trii; int a[N], n, k; trii tr; signed main() { cin >> n >> k; rep(i,1,n) cin >> a[i]; rep(i,1,n) { tr.insert({a[i],i}); if (i > k) tr.erase(tr.lower_bound({a[i-k],0})); if (i>=k) cout << tr.find_by_order((k-1)/2)->ff << "\n "[i+1<=n]; } } ```