1. 程式人生 > 其它 >Range and Partition (構造+尺取)

Range and Partition (構造+尺取)

Problem 1630B - Codeforces

題目大意:

給一個長度為 \(n\) 的陣列 \(a\), 找到一個區間 [x,y], 使得 a 可以分成 k 個子陣列,滿足 3 個條件:

  1. 子陣列是 a 中連續的元素組成的
  2. a 中每個元素分到某個子數組裡
  3. 在每個子陣列中,位於[x,y] 區間內的元素個數要嚴格大於剩餘元素個數。

最小化 \(y - x\)

資料範圍

n 不超過 \(2 \cdot 10^5\) 1<=a[i]<=n

輸入輸出

輸入: 樣例個數t n k 陣列a

輸出: x,y 每個子陣列的兩端

解題思路

  1. 考慮區間 [x,y] 固定時問題的解法。假設有解,那麼考慮構造,只需從頭到尾,每次遍歷到滿足要求的數比剩餘數多1時,劃分出這一個陣列。劃分出(k-1)個數組時,最後一個單獨留出來即可。
  2. 那麼 區間[x,y]在什麼時候有解? 既然要求 每個子陣列在[x,y]內的數目嚴格大,也就是每個子陣列內這樣的數比其他數至少多1。劃分為k個子陣列,則這樣的數比其他數至少多k。所以設整個陣列有\(w\)個數在區間內,若滿足 \(w-(n-w) \ge k\)\(w \ge \frac{n+k}{2}\) 即可。
  3. 因此,可以先遍歷一遍陣列 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;
}