我热爱暴力数据结构!
分块是一种暴力的思想,并非算法。
给出一个长为 的数列。
有 个询问,每行输入四个数字 、、、,以空格隔开。
若 ,表示将位于 的之间的数字都加 。
若 ,表示询问 的值( 和 忽略)。
保证 。。每次操作后的 满足 。
考虑暴力。
我们可以按题目说的模拟,每次遍历 加, 就输出 ,复杂度 ,明显无法通过。
考虑复杂度正确的做法。
我们可以将数组分成一段一段的“块”。
就像这样:
对于每一个块,我们可以去维护它块内元素的和。在求区间加的时候,对于“不完整”的块直接暴力加,中间的“完整块”打上一个懒标记。这样在求 的值的时候,只需要用 加上 所在块的懒标记了。
时间复杂度 。
这个思想听上去不错,但仍然有一些小细节。
如何计算下标对应的块编号?
我们设预定块长为 ,则编号为 。是什么意思呢?我们先将下标转换为 开头的,这样 就是从 开始的块编号,加一就是 开头的了。
如果 不是 的倍数怎么办?
其实最后一个块不一定是完整的,不影响计算。
块长怎么确定?
块长最好是 。
不难发现上述操作区间修改的时间复杂度是 的。
由二元均值不等式,对 有:
令 ,代入得:
等号成立条件:
所以最优块长为 。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=3e5+7;
int a[N],b[N],id[N],n,len,s[N];
void add(int l,int r,int x){
if(id[l]==id[r]){
for(int i=l;i<=r;i++)a[i]+=x,s[id[l]]+=x;//同一块暴力加
return;
}
for(int i=l;id[i]==id[l];i++)a[i]+=x,s[id[l]]+=x;//左边“不完整块”暴力加
for(int i=id[l]+1;i<id[r];i++)b[i]+=x,s[i]+=len*x;//中间大块打懒标记,更新块内元素和
for(int i=r;id[i]==id[r];i--)a[i]+=x,s[id[r]]+=x;//右边“不完整块”暴力加
}
signed main(){
cin>>n;
len=sqrt(n);
for(int i=1;i<=n;i++){
cin>>a[i];
id[i]=(i-1)/len+1;
s[id[i]]+=a[i];
}
for(int i=1;i<=n;i++){
int opt,l,r,c;
cin>>opt>>l>>r>>c;
if(opt==0)add(l,r,c);
else print("{}\n",a[r]+b[id[r]]);//求值
}
}
有 行询问,每行输入四个数字 、、、,以空格隔开。
若 ,表示将位于 的之间的数字都加 。
若 ,表示将位于 的之间的数字都乘 。
若 ,表示询问 的值 ( 和 忽略)。要输出非负的余数值。
考虑如何处理区间乘。
某些群众:我们可以再打个懒标记 呀!
那如何处理区间求和呢?
在打乘法懒标记的时候,我们可以将原来的 懒标记同时乘上 。操作这段区间时下放一下就行了。
如何下放呢?
下放时我们优先下放 ,然后再下放 。
#include<bits/stdc++.h>
using namespace std;
const int N=3e5+7,mod=10007;
#define int long long
int a[N],id[N],mul[N],add[N],n,len;
void reset(int bid){//下放懒标记
for(int i=(bid-1)*len+1;i<=min(bid*len,n);i++){
a[i]=((1ll*a[i]*mul[bid])%mod+add[bid]+mod)%mod;
}//先下放乘,然后加
add[bid]=0;
mul[bid]=1;
}
void Add(int opt,int l,int r,int c){
c%=mod;
if(id[l]==id[r]){
reset(id[l]);
for(int i=l;i<=r;i++){
if(!opt)a[i]=((a[i]+c)%mod+mod)%mod;
else a[i]=((1ll*a[i]*c%mod)+mod)%mod;
}
return;
}
reset(id[l]);
reset(id[r]);
for(int i=l;id[l]==id[i];i++){
if(!opt)a[i]=((a[i]+c)%mod+mod)%mod;
else a[i]=((1ll*a[i]*c%mod)+mod)%mod;
}
for(int i=id[l]+1;i<id[r];i++){
if(!opt)add[i]=(add[i]+c)%mod;
else mul[i]=mul[i]*1ll*c%mod,add[i]=add[i]*1ll*c%mod;
}//add一起乘
for(int i=r;id[i]==id[r];i--){
if(!opt)a[i]=((a[i]+c)%mod+mod)%mod;
else a[i]=((1ll*a[i]*c%mod)+mod)%mod;
}
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n;
len=sqrt(n);
for(int i=1;i<=n;i++){
cin>>a[i];
a[i]%=mod;
id[i]=(i-1)/len+1;
}
for(int i=1;i<=n;i++)mul[i]=1;
for(int i=1;i<=n;i++){
int opt,l,r,c;
cin>>opt>>l>>r>>c;
if(opt<2)Add(opt,l,r,c);
else{
cout<<((a[r]*mul[id[r]]%mod+ add[id[r]])%mod+mod)%mod<<'\n';
}
}
}
第二行输入 个数字,第 个数字为 ,以空格隔开。
有 行询问,每行输入三个数字 ,以空格隔开。
若 ,表示将位于 的之间的数字都开方。对于区间中每个
若 ,表示询问位于 的所有数字的和。
区间开方,发现不能打懒标记。
打我们发现一个性质:一个大数,开 次方,最后也会变成 或 。
所以,开方暴力遍历即可。若该块内元素均已变为 或 ,就跳过这个块。
注意同时维护每个块的和。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=3e5+7;
int a[N],b[N],id[N],n,len,s[N];
bool flg[N];
void upd(int j){
s[id[j]]-=a[j];
a[j]=(int)sqrt(a[j]);
s[id[j]]+=a[j];
}
bool check(int bid){
bool f=1;
int lf=(bid-1)*len+1,ri=min(n,bid*len);
for(int i=lf;i<=ri;i++)if(a[i]>1)f=0;
return f;
}
void sqrted(int l,int r){
if(id[l]==id[r]){
if(flg[id[l]])return;
for(int i=l;i<=r;i++)upd(i);
flg[id[l]]=check(id[l]);
return;
}
if(!flg[id[l]]){
for(int i=l;id[i]==id[l];i++)upd(i);
flg[id[l]]=check(id[l]);
}
for(int i=id[l]+1;i<id[r];i++){
if(!flg[i]){
int lf=(i-1)*len+1,ri=min(n,len*i);
for(int j=lf;j<=ri;j++)upd(j);
flg[i]=check(i);
}
}
if(!flg[id[r]]){
for(int i=r;id[i]==id[r];i--)upd(i);
flg[id[r]]=check(id[r]);
}
}
int query(int l,int r){
int ans=0;
if(id[l]==id[r]){
for(int i=l;i<=r;i++)ans+=a[i]+b[id[l]];
return ans;
}
for(int i=l;id[i]==id[l];i++)ans+=a[i]+b[id[l]];
for(int i=id[l]+1;i<id[r];i++)ans+=s[i];
for(int i=r;id[i]==id[r];i--)ans+=a[i]+b[id[r]];
return ans;
}
signed main(){
cin>>n;
len=sqrt(n);
for(int i=1;i<=n;i++){
cin>>a[i];
id[i]=(i-1)/len+1;
s[id[i]]+=a[i];
}
for(int i=1;i<=n;i++){
int opt,l,r,c;
cin>>opt>>l>>r;
if(opt==0)sqrted(l,r);
else print("{}\n",query(l,r));
}
}
两个正整数 ,分别表示数列中实数的个数和操作的个数。
第二行包含 个实数,其中第 个实数表示数列的第 项。
接下来 行,每行为一条操作,格式为以下三种之一:
操作 :1 x y k ,表示将第 到第 项每项加上 , 为一实数。
操作 :2 x y ,表示求出第 到第 项这一子数列的平均数。
操作 :3 x y ,表示求出第 到第 项这一子数列的方差。
首先,我们根据 ,得到
继续推
因为
所以我们维护一个块内平方和还有块内和就好了。
那如何更新块内平方和呢?
我们发现
等价于
也就是
这一坨其实就是维护的两个和了。
至此,本题解决。
#include<bits/stdc++.h>
#define db double
using namespace std;
const int N=1e5+7;
db a[N],b[N],s[N],s_2[N];
int id[N],len,n,m;
void add(int l,int r,db x){
int L=id[l],R=id[r];
if(L==R){
for(int i=l;i<=r;i++){
s_2[L]-=a[i]*a[i];
a[i]+=x;
s[L]+=x;
s_2[L]+=a[i]*a[i];
}
return;
}
for(int i=l;id[i]==L;i++){
s_2[L]-=a[i]*a[i];
a[i]+=x;
s[L]+=x;
s_2[L]+=a[i]*a[i];
}
for(int i=L+1;i<R;i++)b[i]+=x;
for(int i=r;id[i]==R;i--){
s_2[R]-=a[i]*a[i];
a[i]+=x;
s[R]+=x;
s_2[R]+=a[i]*a[i];
}
}
db query1(int l,int r){
int L=id[l],R=id[r];
db sum=0;
if(L==R){
for(int i=l;i<=r;i++)sum+=a[i]+b[L];
return sum/(r-l+1);
}
for(int i=l;id[i]==L;i++)sum+=a[i]+b[L];
for(int i=L+1;i<R;i++)sum+=s[i]+b[i]*len;
for(int i=r;id[i]==R;i--)sum+=a[i]+b[R];
return sum/(r-l+1);
}
db query2(int l,int r){
int len=r-l+1,pl=id[l],pr=id[r];double ave=0,ans=0;
if(pl==pr){
for(int i=l;i<=r;i++)ave+=a[i]+b[pl],ans+=(a[i]+b[pl])*(a[i]+b[pl]);
return ans/len-(ave/len)*(ave/len);
}
for(int i=l;id[i]==pl;i++)ave+=a[i]+b[pl],ans+=(a[i]+b[pl])*(a[i]+b[pl]);
for(int i=r;id[i]==pr;i--)ave+=a[i]+b[pr],ans+=(a[i]+b[pr])*(a[i]+b[pr]);
for(int i=pl+1;i<pr;i++)ave+=s[i]+b[i]*::len,ans+=s_2[i]+2*s[i]*b[i]+::len*b[i]*b[i];
return ans/len-(ave/len)*(ave/len);
}
int main(){
cin>>n>>m;
len=sqrt(n);
for(int i=1;i<=n;i++){
cin>>a[i];
id[i]=(i-1)/len+1;
s[id[i]]+=a[i];
s_2[id[i]]+=a[i]*a[i];
}
while(m--){
int opt,l,r;
db k;
cin>>opt>>l>>r;
if(opt==1){
cin>>k;
add(l,r,k);
}else if(opt==2){
printf("%.4f\n",query1(l,r));
}else{
printf("%.4f\n",query2(l,r));
}
}
}
Q 的时候,表示这是一条询问操作,它询问 ID 从 U 的时候,表示这是一条更新操作,如果当前 一眼想到 st 表,发现带修改,寄了。
设
我们对每个块去维护最大值
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+7;
int n,m,mx[N],id[N],a[N],len;
int query(int l,int r){
int ans=-1;
if(id[l]==id[r]){
for(int i=l;i<=r;i++)ans=max(ans,a[i]);
return ans;
}
for(int i=l;id[l]==id[i];i++)ans=max(ans,a[i]);
for(int i=id[l]+1;i<id[r];i++)ans=max(ans,mx[i]);
for(int i=r;id[r]==id[i];i--)ans=max(ans,a[i]);
return ans;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n>>m;
len=sqrt(n);
for(int i=1;i<=n;i++){
cin>>a[i];
id[i]=(i-1)/len+1;
mx[id[i]]=max(mx[id[i]],a[i]);
}
while(m--){
char opt;
int l,r;
cin>>opt>>l>>r;
if(opt=='U'){
if(a[l]<r){
a[l]=r;
mx[id[l]]=max(mx[id[l]],r);
}
}else cout<<query(l,r)<<endl;
}
}
给定一个含有
Q l r k 表示查询下标在区间 C x y 表示将 动态区间 kth 板子题。
这玩意儿的静态版本可以用主席树
妈的不会真有人写树套树吧。
考虑静态查询。
我们考虑二分区间第
怎么算这一坨?
先用
小块暴力统计,大块在 upper_bound 就行了。
然后就很板子了。
考虑动态查询。
其实很简单,将
#pragma target optimize(O3)
#pragma target optimize(O2)
#pragma target optimize(O1)
#pragma target optimize(Ofast)
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+7;
int a[N],blk[320][320],siz[320],n,m,id[N],len,sor[320][320],cpy[320];
inline int read(){
int f=0;char ch=getchar();
while(ch<48||ch>57)ch=getchar();
while(ch>47&&ch<58)f=(f<<3)+(f<<1)+(ch^48),ch=getchar();
return f;
}
inline void print(int x){
if(x==0){putchar('0');return;}
if(x>=10)print(x/10);
putchar(x%10+'0');
}
void sorted(int id){
for(int i=1;i<=siz[id];i++)cpy[i]=blk[id][i];
sort(cpy+1,cpy+siz[id]+1);
for(int i=1;i<=siz[id];i++)sor[id][i]=cpy[i];
}
int rk(int l,int r,int x){
int ans=0;
if(id[l]==id[r]){
for(int i=l;i<=r;i++)if(a[i]<=x)ans++;
return ans;
}
for(int i=l;id[l]==id[i];i++)if(a[i]<=x)ans++;
for(int i=id[l]+1;i<id[r];i++)ans+=upper_bound(sor[i]+1,sor[i]+siz[i]+1,x)-sor[i]-1;
for(int i=r;id[r]==id[i];i--)if(a[i]<=x)ans++;
return ans;
}
int kth(int l,int r,int k){
int L=0,R=1e9+7,ans=-1;
while(L<=R){
int mid=(L+R)>>1;
if(rk(l,r,mid)>=k){
ans=mid;
R=mid-1;
//if(mid==6)cout<<rk(l,r,6)<<endl;
}else L=mid+1;
}
return ans;
}
signed main(){
n=read();
m=read();
len=sqrt(n);
for(int i=1;i<=n;i++){
a[i]=read();
id[i]=(i-1)/len+1;
blk[id[i]][++siz[id[i]]]=a[i];
}
for(int i=1;i<=(n+len-1)/len;i++)sorted(i);
while(m--){
char c;
int l,r,k;
cin>>c;
l=read();
r=read();
if(c=='C'){
int p=l-(id[l]-1)*len;
blk[id[l]][p]=r;
a[l]=r;
sorted(id[l]);
//for(int i=1;i<=siz[id[l]];i++)cout<<sor[id[l]][i]<<' ';
//cout<<endl;
}
else{
k=read();
print(kth(l,r,k));
putchar('\n');
}
}
}
给你一个长为
依旧简单题。
依旧整体二分。
我们算
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,m,len=500,a[N],b[N],tag[N];
void sorted(int idx){
int l=idx*len;
int r=min((idx+1)*len,n);
memcpy(b+l,a+l,(r-l)<<2);
sort(b+l,b+r);
}
void add(int l,int r,int x){
int L=l/len;
int R=r/len;
if(L==R){
for(int i=l;i<=r;i++) a[i]+=x;
sorted(L);
return;
}
int ll=(L+1)*len;
for(int i=l;i<ll;i++) a[i]+=x;
sorted(L);
for(int i=L+1;i<R;i++) tag[i]+=x;
int rr=R*len;
for(int i=rr;i<=r;i++) a[i]+=x;
sorted(R);
}
int rk(int l,int r,int x){
int L=l/len;
int R=r/len;
int ans=0;
if(L==R){
for(int i=l;i<=r;i++) ans+=a[i]+tag[L]<=x;
return ans;
}
int ll=(L+1)*len;
for(int i=l;i<ll;i++) ans+=a[i]+tag[L]<=x;
for(int i=L+1;i<R;i++){
ans+=upper_bound(b+i*len,b+(i+1)*len,x-tag[i])-b-i*len;
}
int rr=R*len;
for(int i=rr;i<=r;i++) ans+=a[i]+tag[R]<=x;
return ans;
}
int query(int l,int r,int k){
int L=-2e4-2e9-1,R=2e9+2e4+1,ans=-1;
while(L<=R){
int mid=((long long)L+R)/2;
if(rk(l,r,mid)>=k){
ans=mid;
R=mid-1;
}else L=mid+1;
}
return ans;
}
int main(){
ios::sync_with_stdio(0);cin.tie(0);
cin>>n>>m;
for(int i=0;i<n;i++) cin>>a[i];
for(int i=0;i*len<n;i++) sorted(i);
while(m--){
int opt,l,r,k;
cin>>opt>>l>>r>>k;
l--;r--;
if(opt==2) add(l,r,k);
if(opt==1) cout<<query(l,r,k)<<'\n';
}
}
此代码常数巨大,不开 C++98 过不了,测试点几乎是擦着时限过去的。