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;
}

posted @ 2022-02-10 14:04  HIVM  阅读(154)  评论(1编辑  收藏  举报