CF1630B Range and Partition

首先看到最小化 y − x y-x yx 这个玩意,果断二分 y − x y-x yx,显然假如区间更长合法,那么更短一定也合法,然后再枚举 x x x 进而得到 [ x , y ] [x,y] [x,y],接下来的事情就是要判断某一个区间 [ x , y ] [x,y] [x,y] 是否合法。

我们首先把砍成 k k k 段转化为尽可能地多段,因为如果 t t t 段的分割是合法的,我们我们将最后两个区间合并,这就是 t − 1 t-1 t1 段了。

我们将 a i a_i ai 重新赋值,如果 a i ∈ [ x , y ] a_i \in [x,y] ai[x,y] 那么赋值为 1,否则为 -1,这样我们分割的 k k k 段区间每一段的 a i a_i ai 的和满足是正数即可,鉴于这是区间求和,搞个前缀和 s i = ∑ j = 1 i a j s_i=\sum\limits_{j=1}^ia_j si=j=1iaj,条件变成了 s r − s l − 1 > 0 , s r > s l − 1 s_r-s_{l-1}\gt0,s_r>s_{l-1} srsl1>0,sr>sl1 l − 1 l-1 l1 为刚好上一个区间的右端点,也就是说我们在求一个 s s s 的上升子序列并且长度为 k + 1 k+1 k+1,起点为 s 0 = 0 s_0=0 s0=0,终点为 s n s_n sn。于是搞完这一切发现复杂度还是不行。

但是我们发现其实还可以抢救一下,我们发现 s s s 有个奇妙的性质,就是 ∣ s i − s i − 1 ∣ = 1 |s_i-s_{i-1}|=1 sisi1=1,所以如果 s s s 里有 x > 0 x>0 x>0,那么 s s s 一定还有 0 , 1 , 2 ⋯ x − 2 , x − 1 0,1,2\cdots x-2,x-1 0,1,2x2,x1,并且下标存在在其之前的。这个最长上升子序列是可以构造出来的,只需要中间是连续的正整数。还有最后一个条件那就是序列长度要有 k + 1 k+1 k+1,这很简单只要让 s n ≥ k s_n\ge k snk 就行。所以我们判定区间合不合法只需要计算一个 s n s_n sn 就完了。

回顾 s n s_n sn 定义,我们令 t = ∑ i = 1 n [ a i ∈ [ x , y ] ] t=\sum\limits_{i=1}^n[a_i \in [x,y]] t=i=1n[ai[x,y]],显然 s n = t − ( n − t ) = 2 t − n s_n=t-(n-t)=2t-n sn=t(nt)=2tn,也就是在 [ x , y ] [x,y] [x,y] 的数的个数减去不在的。我们再用一个前缀和便于查询在某个值域区间的数的个数来计算 s n s_n sn,于是这题就结束了,吗?

我当时以为到这就没了,结果发现题目还让我去求方案,人傻了。这其实也简单,既然都知道了 [ x , y ] [x,y] [x,y],我们可以按照上升子序列的求解方式搞,也可以直接贪心,因为保证了 [ x , y ] [x,y] [x,y] 合法,所以我们区间的和为正的时候就划分一下,显然这是对的。

枚举 [ x , y ] [x,y] [x,y] 貌似也可以用双指针,但是我不会。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define cmin(a, b) (a)=min((a), (b))
#define cmax(a, b) (a)=max((a), (b))
#define mem(a, x) memset(a, x, sizeof(a))
#define rps(i, b, e) for(int i=(b);i<=(e);i++)
#define prs(i, e, b) for(int i=(e);i>=(b);i--)
#define rpg(i, g, x) for(int i=g.head[x];i;i=g.e[i].nxt)
#define opf(s) freopen(s".in", "r", stdin), freopen(s".out", "w", stdout)
using namespace std;
template<class T>
inline void read(T &x) {
	x=0;
	bool f=false;
	char c=getchar();
	while(!isdigit(c))f|=(c=='-'), c=getchar();
	while(isdigit(c))x=x*10+(c-'0'), c=getchar();
	if(f)x=-x;
}
const int NR=2e5+10;
int T, n, k, a[NR], s[NR];
int chk(int dif) {
	rps(l, 1, n-dif+1)
		if((s[l+dif]-s[l-1])*2-n>=k)
			return l;
	return 0;
}
struct node {
	int id, v, ct;
}st[NR], p[NR];
int top;
int main() {
	read(T);
	while(T--) {
		read(n), read(k);
		mem(s, 0);
		rps(i, 1, n)read(a[i]), s[a[i]]++;
		rps(i, 1, n)s[i]+=s[i-1];
		int l=0, r=n-1, mid, yx=1e9, xx=1e9;
		while(l<=r) {
			mid=l+r>>1;
			int res=chk(mid);
			if(res==0)l=mid+1;
			else yx=mid, xx=res, r=mid-1;
		}
		printf("%d %d\n", xx, xx+yx);
		int yy=xx+yx;
		l=1;
		int subct=0, ns=0;
		rps(i, 1, n) {
			if(a[i]>=xx && a[i]<=yy)ns++;
			else ns--;
			if(ns<=0 || subct+1>=k)continue;
			printf("%d %d\n", l, i);
			l=i+1, subct++, ns=0;
		}
		printf("%d %d\n", l, n);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值