倍增算法
1.对于本题,如果我们采用二分的方法,最坏时间复杂度是
O
(
n
2
l
o
g
n
)
O(n^2logn)
O(n2logn)。
证明:最坏的情况是每次往后走一个位置,一共需要走n次,每次需要花费的时间是:
n
/
2
l
o
g
(
n
/
2
)
+
n
/
4
l
o
g
(
n
/
4
)
+
.
.
.
+
1
l
o
g
1
≤
n
l
o
g
n
n/2log(n/2)+n/4log(n/4)+...+1log1\leq nlogn
n/2log(n/2)+n/4log(n/4)+...+1log1≤nlogn
因此时间复杂复杂度为
n
∗
n
l
o
g
n
=
n
2
l
o
g
n
n*nlogn=n^2logn
n∗nlogn=n2logn。
2.如果采用倍增的方法,可以这么来写:
- 初始化p=1,R=L
- 求出[L,R+p]这一段区间的校验值,如果校验值 ≤ T \leq T ≤T,则R+=p,p*=2否则p/=2
- 重复上面每一步,直到p=0,R为所求当前段的最右位置。
对于普通的倍增时间复杂度为
O
(
n
l
o
g
2
n
)
O(nlog^2n)
O(nlog2n)
证明:对于找到一段符合条件的len1(长度逐渐变长直到len1),最多需要倍增log(n)次,这样对于一段的排序总共需要<log(n)len1log(len1)的时间(因为在len1之前的长度都比他短)。总共的时间为
l
o
g
(
n
)
(
l
e
n
1
l
o
g
(
l
e
n
1
)
+
…
+
l
e
n
k
l
o
g
(
l
e
n
k
)
)
<
l
o
g
(
n
)
n
l
o
g
(
n
)
log(n)(len1log(len1)+…+lenklog(lenk))<log(n)nlog(n)
log(n)(len1log(len1)+…+lenklog(lenk))<log(n)nlog(n)
3.如果我们每次求校验值时不用快速排序,而是采用归并排序的思想,只对新增的长度部分排序,然后合并新旧两端,这样总体时间复杂度可以达到 O ( n l o g n ) O(nlogn) O(nlogn)。
#include<iostream>
#include<algorithm>
using namespace std;
#define ll long long
#define MAX_N 500000
ll a[MAX_N+5],w[MAX_N+5],temp[MAX_N+5];
int k;
int n,m;
ll t;
bool check(int l,int mid,int r)
{
for(int i=mid+1;i<=r;i++)
w[i]=a[i];
sort(w+mid+1,w+r+1);
int i=l,j=mid+1,k=l;
while(i<=mid&&j<=r)
{
if(w[i]<=w[j])temp[k++]=w[i++];
else temp[k++]=w[j++];
}
while(i<=mid)temp[k++]=w[i++];
while(j<=r)temp[k++]=w[j++];
long long sum=0;
for(int i=l,j=r,cnt=0;cnt<m&&i<j;cnt++,i++,j--)
sum+=(temp[i]-temp[j])*(temp[i]-temp[j]);
if(sum<=t)return 1;
else return 0;
}
int main()
{
cin>>k;
while(k--)
{
cin>>n>>m>>t;
for(int i=1;i<=n;i++)
scanf("%lld",a+i);
w[1]=a[1];
int st=1,ed=1,len=1,ans=0;
while(ed<n)
{
len=1;
while(len)
{
if(ed+len<=n&&check(st,ed,ed+len))
{
ed+=len;
len<<=1;
for(int i=st;i<=ed;i++)
w[i]=temp[i];
}
else len>>=1;
}
st=ed+1;
ans+=1;
}
cout<<ans<<endl;
}
return 0;
}
ST算法
在RMQ问题(区间最值问题)中,著名的ST算法就是倍增的产物。ST算法能在 O ( n l o g n ) O(nlogn) O(nlogn)的时间预处理后,以 O ( 1 ) O(1) O(1)的时间复杂度在线回答“数列A中下标在l~r之间的数的最大值是多少这样的问题”。
预处理:
void ST_prework()
{
for(int i=1;i<=n;i++)f[i][0]=a[i];
int t=log(n)/log(2)+1;
for(int j=1;j<t;j++)
for(int i=1;i<=n-(1<<j)+1;i++)
f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
其中 f [ i ] [ j ] f[i][j] f[i][j]表示数列A中的下标在子区间 [ i , i + 2 j − 1 ] [i,i+2^j-1] [i,i+2j−1]里的数的最大值。
查询:
int ST_query(int l,int r)
{
int k=log(r-l+1)/log(2);
return max(f[l][k],f[r-(1<<k)+1][k]);
}
查询任意区间 [ l , r ] [l,r] [l,r]内的最值时,需要先计算出一个k,满足 2 k ≤ r − l + 1 < 2 k + 1 2^k \leq r-l+1 <2^{k+1} 2k≤r−l+1<2k+1, f [ l , k ] f[l,k] f[l,k]和 f [ r − 2 k + 1 , k ] f[r-2^k+1,k] f[r−2k+1,k]中的较大的值就是区间 [ l , r ] [l,r] [l,r]内的最值。
注:log函数效率较高,一般对程序性能影响不大。为了保证复杂度为O(1),可以O(n)预处理出1~n种区间长度各自对应的k值。