大家都知道在OI算法当中复杂度大都是O(N)、O(N^2)、O(NlogN)......比较常见,而O(sqrt(n))的复杂度并不常见,只有分块、朝鲜树(似乎并不常用但确实复杂度带根号)但是在某些意想不到的地方,可以使用根号算法,不仅效果不错,还比较好写。
引出我们今天的题目CODEVS 3550,已知序列a_1,a_2,...a_i...,a_n,可以进行单点修改,(输入格式为(0,x,y)三元组,意即a_x=a_x+y),可以进行极值查询(输入格式为(1,x,d)三元组,意即查询a_x,a_x+d,a_x+2*d...a_x+k*d中最大值,其中满足x+k*d<=n并且x+(k+1)*d>n(通俗一点来说就是,查询一个首项为x,公差为d的等差数列的数组下标集合所对应的元素极值)
我们所学过的数据结构大多支持连续的区间查询,这样零散的数据怎么办呢。我们可以发现,如果公差d比较小,我们可以对于每种公差d建立d棵线段树,分别保存首项为1,2,......,n-1,n公差为d的下标集合所对应的数;但这种方法在d较大时会导致空间和时间的浪费。如果公差d比较大,我们可以直接暴力枚举。于是很容易想到把两种方法结合起来。那么问题来了,d的分界线在哪呢。
设d的分界限为K,序列长度为N,操作数为K,可以算出时间复杂度为O(N*K*logN(建树)+M*logN(线段树查询)+M*N/K(暴力查询)+M*K*logN(线段树修改)),我们发现这是一个对勾函数,当K=sqrt(M*N/(M+N)*logN),考虑到N与M大致相同,简化为sqrt(M/logN)。这样算法的复杂度变为O(M*sqrt(M*logN))。根号便奇怪的出现在了时间复杂度当中。
在实际实现过程当中,由于线段树常数过大,K应取小一点。
code:(考虑到上面的K太难算了,我取了个常数15= =)
#include<iostream>
#include<cstdio>
#include<cstring>
#define mid (l+r)/2
#define lch t,i<<1,l,mid
#define rch t,i<<1|1,mid+1,r
using namespace std;
int node[121][280001],n,m;
int a[70001],b[70001];
int f[16][16][2],ans;
void updata(int t,int i)
{
node[t][i]=max(node[t][i<<1],node[t][i<<1|1]);
}
void build(int t,int i,int l,int r)
{
if (l==r)
{
node[t][i]=b[l];
return;
}
build(lch); build(rch);
updata(t,i);
}
void insert(int t,int i,int l,int r,int x,int y)
{
if (l==r&&l==x)
{
node[t][i]+=y;
return;
}
if (x<=mid) insert(lch,x,y);
else insert(rch,x,y);
updata(t,i);
}
void query(int t,int i,int l,int r,int x,int y)
{
if (x<=l&&y>=r)
{
ans=max(ans,node[t][i]);
return;
}
if (x<=mid) query(lch,x,y);
if (y>mid) query(rch,x,y);
}
int main()
{
int n,m,i,j,k,t=0,opt,x,y,start,place,st;
freopen("c.in","r",stdin);
freopen("c.out","w",stdout);
scanf("%d",&n);
for (i=1;i<=n;++i)
scanf("%d",&a[i]);
for (i=1;i<=min(15,n);++i)
{
for (j=1;j<=i;++j)
{
b[0]=0;
t++;
f[i][j%i][0]=t;
for (k=j;k<=n;k+=i)
b[++b[0]]=a[k];
f[i][j%i][1]=b[0];
build(t,1,1,b[0]);
}
}
scanf("%d",&m);
for (i=1;i<=m;++i)
{
scanf("%d%d%d",&opt,&x,&y);
if (opt==0)
{
for (j=1;j<=min(15,n);++j)
{
st=start=x%j; if (!st) st+=j;
if (x%j==0) place=(x-st)/j+1;
else place=(x-st+1)/j+1;
insert(f[j][start][0],1,1,f[j][start][1],place,y);
}
a[x]+=y;
}
if (opt==1)
{
ans=-2147483647;
if (y<=15)
{
st=start=x%y; if (!st) st+=y;
if (x%y==0) place=(x-st)/y+1;
else place=(x-st+1)/y+1;
query(f[y][start][0],1,1,f[y][start][1],place,f[y][start][1]);
printf("%d\n",ans);
}
else
{
for (j=x;j<=n;j+=y)
ans=max(ans,a[j]);
printf("%d\n",ans);
}
}
}
}这道其实是CF#80 DIV1 D的加强版,原题为求和且并无修改操作。。。原题做法参见王悦同2014年国家集训队论文——《根号算法——不只是分块》
1219



被折叠的 条评论
为什么被折叠?



