Range and Partition (构造+尺取)

Problem 1630B - Codeforces

题目大意:

给一个长度为 n 的数组 a, 找到一个区间 [x,y], 使得 a 可以分成 k 个子数组,满足 3 个条件:

  1. 子数组是 a 中连续的元素组成的
  2. a 中每个元素分到某个子数组里
  3. 在每个子数组中,位于[x,y] 区间内的元素个数要严格大于剩余元素个数。

最小化 yx

数据范围

n 不超过 2105 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(nw)kwn+k2 即可。
  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 @   HIVM  阅读(163)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示