标签:端点 style long script cst == 指南 集合 iostream
题目链接:https://www.acwing.com/problem/content/description/111/
首先定义了集合S的校验值,取出m对数,使得每对平方之后求和最大,这个值成为集合S的校验值。现在给定一个数列,求满足每段的校验值小于T的前提下最小能把数列分成连续的几段?
利用倍增的思想对右端点进行倍增,并且结合归并排序去除冗余的排序。具体解释在代码中阐明。二进制就是这么神奇而高效。
代码如下:
#include<cstdio> #include<iostream> #include<algorithm> using namespace std; #define maxn 500010 typedef long long ll; int n,m,ed; ll k;//注意k是1e18范围的 ll a[maxn],b[maxn],c[maxn];//a:无序,原数组,b:分两段有序,c:归并之后的合并数组 void gb(int l,int mid,int r){//[l,mid]与[mid+1,r] 归并, int i=l,j=mid+1;//双指针扫描 for(int k=l;k<=r;k++){ if(j>r || (i<=mid && b[i] <= b[j]))c[k]=b[i++]; else c[k]=b[j++]; } } ll calc(int l,int r){//计算校验值 if(r>n)r=n; int t=min((r-l+1)/2,m);//将[l,r]区间分成的最大的对数 ll ans=0; for(int i=ed+1;i<=r;i++) b[i]=a[i];//将a[i]中上次mid+1的位置到r的序列放入b中并排序 sort(b+ed+1,b+r+1);//[ed+1,r]部分排序 gb(l,ed,r); for(int i=0;i<t;i++){ ans+=(c[r-i]-c[l+i])*(c[r-i]-c[l+i]); } return ans; } void Genius_ACM(){ int ans=0;//分的段数 int l=1,r=1; b[1]=a[1]; ed=1;//ed表示的是上次倍增的结束位置 while(l<=n){ int p=1;//每一段都是从2^0开始倍增的 while(p){ ll num=calc(l,r+p); if(num<=k){ ed=r=min(r+p,n); for(int i=l;i<=r;i++)b[i]=c[i];//将归并的有序序列放入b中 if(r==n)break; p<<=1; }else p>>=1;//倍增失败,缩小倍增跨度 } ans++; l=r+1; } cout<<ans<<endl; } int main(){ int t; cin>>t; while(t--){ cin>>n>>m>>k; for(int i=1;i<=n;i++)scanf("%lld",&a[i]); Genius_ACM(); } return 0; }
《算法竞赛进阶指南》0x06倍增 Acwing GeniusACM
标签:端点 style long script cst == 指南 集合 iostream
原文地址:https://www.cnblogs.com/randy-lo/p/13137638.html