【ContestHunter0601】Genius ACM-贪心+倍增+归并排序

测试地址:Genius ACM
做法: 本题需要用到贪心+倍增+归并排序。
某机房大佬给的我他书上的一道神题…据说还是“基础算法”章节的例题…看来我NOIP退役已经是可以预见的了…
首先可以大胆猜想(并小心证明)的是,计算校验值时所选的 M M M对数,一定是最大的与最小的配对,次大的与次小的配对…以此类推。那么很明显的,一个区间如果被另一个区间包含,那么被包含的区间的校验值一定更小,这就是区间包含单调性,因此要求至少要分多少段,只要从头开始暴力向右扩展,扩展不了了就分段即可。
那么现在问题的关键是,如何在这个算法的过程中快速地算出校验值?我们发现这种信息用数据结构很难维护,于是我们先思考一个暴力:右端点每扩展一步,就重新对当前区间内的元素排一次序。使用插入排序的话,上述算法最坏情况下是 O ( n 2 ) O(n^2) O(n2)的。于是我们思考,产生重复性的关键问题在哪里呢?显然,如果每次仅插入一个元素,排序的次数很大,重复性也会很高。因此,我们尝试使用倍增的思路,每次加入 2 k 2^k 2k个元素,来降低排序的次数。
一个很明显的思路是,像一般的倍增一样,从大到小枚举 k k k,然后check一下区间 [ L , R + 2 k ] [L,R+2^k] [L,R+2k]合不合法,如果合法就给 R R R加上 2 k 2^k 2k。而check时,我们能想到的最好的方法就是,对 [ R + 1 , R + 2 k ] [R+1,R+2^k] [R+1,R+2k]排序,然后把这个区间和我们已经求出的 [ L , R ] [L,R] [L,R]进行归并。但这样的问题是,check的时间复杂度是 O ( 2 k ⋅ k + ( R + 2 k − L + 1 ) ) O(2^k\cdot k+(R+2^k-L+1)) O(2kk+(R+2kL+1))的,整个算法中要check的次数也较多,姑且算 O ( n ) O(n) O(n)的级别,那也是会爆炸的。因此我们需要使用一种更改过的倍增算法,如下:
1.一开始 R = L , p = 1 R=L,p=1 R=L,p=1
2.判断 [ L , R + p ] [L,R+p] [L,R+p]合不合法,合法则更新 R R R R + p R+p R+p,然后令 p = 2 p p=2p p=2p,否则令 p = p / 2 p=p/2 p=p/2,重复。
3.当上述步骤执行到 p = 0 p=0 p=0时,算法结束。
我们来看一下这个算法比传统倍增好在哪里。首先,可以肯定的是任何情况下,check的次数都为 O ( log ⁡ s ) O(\log s) O(logs)级别(对求出一次分段点而言),其中 s s s为最终分出的段长。然后,这个算法中的 p p p先从小到大,然后再从大到小,这就避免了check复杂度中的那个 2 k 2^k 2k过大。显然 p p p不会超过 2 s 2s 2s。这样一来我们再来分析这个算法的时间复杂度。对求出一次分段点而言,令分出的这一段长度为 s s s,那么因为 p p p不会超过 2 s 2s 2s,所以扩展时check的时间复杂度中, 2 k ⋅ k 2^k\cdot k 2kk这样的部分的总和是 O ( s log ⁡ s ) O(s\log s) O(slogs)的级别,常数会稍大一些。而因为check最多进行 O ( log ⁡ s ) O(\log s) O(logs)次,那么check复杂度后面那个部分的总和也是 O ( s log ⁡ s ) O(s\log s) O(slogs)的级别。那么对于整个序列,check的时间复杂度总和就是 O ( n log ⁡ n ) O(n\log n) O(nlogn),这也就是算法的总时间复杂度了。于是这个问题就被完美的解决了。
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int T,n,m,nowsiz;
ll k,a[500010],now[500010],t[500010],tmp[500010];

bool check(int L,int R)
{
	if (R>n) return 0;
}

bool check(int L,int R,int p)
{
	if (R+p>n) return 0;
	for(int i=1;i<=p;i++)
		t[i]=a[R+i];
	sort(t+1,t+p+1);
	int id1=0,id2=0;
	for(int i=1;i<=R+p-L+1;i++)
	{
		if (id1>=nowsiz) tmp[i]=t[++id2];
		else if (id2>=p) tmp[i]=now[++id1];
			 else if (now[id1+1]<t[id2+1]) tmp[i]=now[++id1];
			 	  else tmp[i]=t[++id2];
	}
	ll ans=0;
	for(int i=1,j=R+p-L+1;i<j&&i<=m;i++,j--)
		ans+=(tmp[j]-tmp[i])*(tmp[j]-tmp[i]);
	return ans<=k;
}

int solve(int L)
{
	int R=L,p=1;
	now[1]=a[L],nowsiz=1;
	while(p)
	{
		if (check(L,R,p))
		{
			R+=p;
			nowsiz=R-L+1;
			for(int i=1;i<=nowsiz;i++)
				now[i]=tmp[i];
			p<<=1;
		}
		else p>>=1;
	}
	return R;
}

int main()
{
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d%lld",&n,&m,&k);
		for(int i=1;i<=n;i++)
			scanf("%lld",&a[i]);
		
		int st=1,ans=0;
		while(st<=n)
		{
			st=solve(st)+1;
			ans++;
		}
		printf("%d\n",ans);
	}
	
	return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值