Range and Partition (構造+尺取)
阿新 • • 發佈:2022-02-10
題目大意:
給一個長度為 \(n\) 的陣列 \(a\), 找到一個區間 [x,y], 使得 a 可以分成 k 個子陣列,滿足 3 個條件:
- 子陣列是 a 中連續的元素組成的
- a 中每個元素分到某個子數組裡
- 在每個子陣列中,位於[x,y] 區間內的元素個數要嚴格大於剩餘元素個數。
最小化 \(y - x\)
資料範圍
n 不超過 \(2 \cdot 10^5\) 1<=a[i]<=n
輸入輸出
輸入: 樣例個數t n k 陣列a
輸出: x,y 每個子陣列的兩端
解題思路
- 考慮區間 [x,y] 固定時問題的解法。假設有解,那麼考慮構造,只需從頭到尾,每次遍歷到滿足要求的數比剩餘數多1時,劃分出這一個陣列。劃分出(k-1)個數組時,最後一個單獨留出來即可。
- 那麼 區間[x,y]在什麼時候有解? 既然要求 每個子陣列在[x,y]內的數目嚴格大,也就是每個子陣列內這樣的數比其他數至少多1。劃分為k個子陣列,則這樣的數比其他數至少多k。所以設整個陣列有\(w\)個數在區間內,若滿足 \(w-(n-w) \ge k\) 即 \(w \ge \frac{n+k}{2}\) 即可。
- 因此,可以先遍歷一遍陣列 a ,得到每個數的數目的陣列 b ,然後用尺取法遍歷 b ,得到差最小的 x , y。最後再用x,y劃分出最終結果。
複雜度 \(O(n)\)
程式碼
#include<bits/stdc++.h> using namespace std; #define ll long long #define ull unsigned long long int n,k; int a[200005]; int b[200005]; int x,y; signed main() { int t;cin >> t; while(t--) { cin >> n >> k; for(int i=1;i<=n;++i) { cin >> a[i]; b[i] = 0; } for(int i=1;i<=n;++i) b[a[i]]++; int le=1,ri=1; int sum=0,p=ceil((double)(n+k)/2.0); int minlen = 1234456778; while(ri<=n) { while(ri<=n&&sum<p) sum += b[ri++]; if(sum>=p&&ri-le<minlen) {minlen=ri-le;x=le;y=ri-1;} while(le<ri&&sum>=p) { sum -= b[le++]; if(sum>=p&&ri-le<=minlen) {minlen=ri-le;x=le;y=ri-1;} } } cout << x << " " << y << endl; int ins=0,outs=0; int num = 0; le = 1; for(int i=1;i<=n;++i) { if(num==k-1) break; if(a[i]<=y&&a[i]>=x) ins++; else outs++; if(ins>outs){ cout << le << " " << i << endl; le = i + 1; ins = outs = 0; num++; } } cout << le << " " << n << endl; } return 0; }