天才ACM

定义一个序列的价值为序列中m对数的差的平方的和的最大值
给定一个序列 求把这个序列至少分成多少段,才能保证每一段的价值都不超过T

首先可以想到是答案具有单调性 二分答案 但是不好检验 所以我们需要换思路
根据排序不等式,这个值是将这个数组后排序后每次取最大的和最小的来累加答案
再经过分析可以发现 我们只需要让每个从左端点开始的序列在满足题意的前提下尽可能长
对于这种求右端点的问题 有两种优化思路:二分和倍增

对于二分法:有可能在L~N上二分 结果右端点只增长了一点
最坏情况下的复杂度是
\(O(n^2\times\log n)\)

复杂度计算详解

对于倍增:
倍增法可以看做两个部分: 增长期和减少期 两个时期对称 只考虑一遍的复杂度计算就可以
我们采用类似归并的方法 排序一边后 两个有序数列合并 这样每个段只被排序了一次 所以时间复杂度是:\(O(\log n+n\log n + n)=O(n\log n)\)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define ll long long
using namespace std;
const int N=500010;

template <typename T>
T read()
{
    T x=0,f=0,c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return f?-x:x; 
}
int n,T,m;
ll lim,a[N],b[N],tmp[N];

int main()
{
	T=read<int>();
	while(T--)
	{
		fill(tmp+1,tmp+n+1,0);
		n=read<int>(); m=read<int>(); lim=read<ll>();
		for(int i=1;i<=n;i++) b[i]=a[i]=read<ll>();
		int ans=0,r=1,l=1;
		while(l<=n)
		{
			bool flag;
			for(int p=1;p;flag?r+=p,p*=2:p=p/2)//注意数组不能越界 
			{
				int R=min(r+p,n);
				if(r+1>R) break;
				sort(b+r+1,b+R+1);
				int t1=l,t2=r+1;
				for(int k=l;k<=R;k++)
					if( (t2>R)||(t1<=r&&b[t1]<=b[t2]) )	tmp[k]=b[t1++];
					else tmp[k]=b[t2++];
				ll ret=0;
				for(int i=1;i<=min(m,(R-l+1)/2);i++)
					ret+=(tmp[R-i+1]-tmp[l+i-1])*(tmp[R-i+1]-tmp[l+i-1]);
				if(flag=(ret<=lim)) for(int i=l;i<=R;i++) b[i]=tmp[i];
				else for(int i=r+1;i<=R+1;i++) b[i]=a[i];
			}
			ans++;	l=r+1; r=l;
		}
		printf("%d\n",ans);
	}
	return 0;
}

技巧:

  1. 有序数组合并来避免整体排序
  2. c++模板的使用
    注意:
    在倍增过程中 特别要注意越界的问题 因此要对有边界取min
    取min后有可能会出现r+1>n的情况 这种情况往往出现在:
    A. 上次有右边界已经把区间全部包括了 这个时候break就行
    B.上次区间到了n-1 这次只剩下一个元素 break累加答案就行
posted @ 2021-12-30 15:08  __iostream  阅读(43)  评论(0)    收藏  举报