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

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

#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;
}
posted @ 2018-10-01 11:41  Maxwei_wzj  阅读(209)  评论(0编辑  收藏  举报