1. 程式人生 > >【ContestHunter0601】Genius ACM-貪心+倍增+歸併排序

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