艰苦地刷了4天半的分块
深感分块是一个非常巧(暴)妙(力) 的算法
如果有觉得hzwer的代码太奇妙(看不懂)的推荐一下机房大佬的通俗易懂的代码:
http://www.cnblogs.com/CHerish_OI/category/1176577.html(此处手动艾特cherish_oi同学)
http://hzwer.com/8053.html
https://loj.ac/problem/6277
区间加法,单点查询
基础的东西
数学老师曰:基本知识不熟悉,难题不会做!
分块 每个块的大小都是sqrt(n)
n=12 N=sqrt(12) 向下取整 3
第一块:1~3 第二块:4~6 第三块:7~9 第四块: 10~12
属于第几块pos[i]=(i-1)/N+1;
如果是完整的块就直接更新到标记里
不完整的块就直接暴力更新就ok
#include
#include
#include
#include
using namespace std;
int m,pos[51000];
int s[51000],tag[51000];
void change(int a,int b,int c)
{
for (int i=a;i<=min(b,pos[a]*m);i++) s[i]+=c;//如果b在a的块内就更新到b 否则更新到a的块的结尾
if (pos[a]!=pos[b])
{
for (int i=(pos[b]-1)*m+1;i<=b;i++) s[i]+=c; //如果ab不同块 更新b的块内的开头到b
}
for (int i=pos[a]+1;i<=pos[b]-1;i++) tag[i]+=c;
}
int main()
{
int n;
scanf("%d",&n);
m=sqrt(n);
for (int i=1;i<=n;i++) pos[i]=(i-1)/m+1;
for (int i=1;i<=n;i++) scanf("%d",&s[i]);
for (int i=1;i<=n;i++)
{
int x,a,b,c;
scanf("%d%d%d%d",&x,&a,&b,&c);
if (x==0)
{
change(a,b,c);
}
else if (x==1)
{
printf("%d\n",tag[pos[b]]+s[b]);
}
}
return 0;
}
https://loj.ac/problem/6278
区间加法,查询比x小的个数
排序维护一下块内的递增性
完整的块可以直接用lower_bound返回一下第一个大于等于x的位置
不完整的块还是直接暴力
非常不争气的写了STL
#include
#include
#include
#include
#include
#include
using namespace std;
int n,N;
int pos[51000],s[51000],tag[51000];
vector<int> v[510];
void update(int x)
{
v[x].clear();
for (int i=(x-1)*N+1;i<=min(x*N,n);i++)//有可能这是最后一个块 min一下
v[x].push_back(s[i]);//push_back:压进容器尾部
sort(v[x].begin(),v[x].end());//从容器头到容器尾排序
}
void add(int x,int y,int c)
{
for (int i=x;i<=min(pos[x]*N,y);i++)//x,y有可能同块 否则更新完x块剩下整个快
s[i]+=c;
update(pos[x]);
if (pos[x]!=pos[y])
{
for (int i=(pos[y]-1)*N+1;i<=y;i++) //更新y所在的不完整块
s[i]+=c;
update(pos[y]);
}
for (int i=pos[x]+1;i<pos[y];i++) tag[i]+=c;
}
int getsum(int x,int y,int c)//x~y中统计小于c个数
{
int ans=0;
for (int i=x;i<=min(pos[x]*N,y);i++)
if (s[i]+tag[pos[x]]//x块
if (pos[x]!=pos[y])//y
{
for (int i=((pos[y]-1)*N+1);i<=y;i++) if (s[i]+tag[pos[y]]for (int i=pos[x]+1;i<pos[y];i++)
{
int t=c-tag[i];
ans+=lower_bound(v[i].begin(),v[i].end(),t)-v[i].begin();//lower_bound:返回x的后继(第一个比x大于等于的位置)(v内有序)
}
return ans;
}
int main()
{
scanf("%d",&n);
N=sqrt(n);
for (int i=1;i<=n;i++) scanf("%d",&s[i]);
for (int i=1;i<=n;i++)
{
pos[i]=(i-1)/N+1;
v[pos[i]].push_back(s[i]);
}
for (int i=1;i<=pos[n];i++)
sort(v[i].begin(),v[i].end());
for (int i=1;i<=n;i++)
{
int u,x,y,c;
scanf("%d%d%d%d",&u,&x,&y,&c);
if (u==0) add(x,y,c);
else if (u==1) printf("%d\n",getsum(x,y,c*c));
}
return 0;
}
https://loj.ac/problem/6279
区间加法,求某个数的前驱
用set维护方便很多(其实相当于一颗splay把我觉得,手动艾特lynkin同学用的二分查找前驱过了)
依旧是完整的块直接lower_bound
不完整的块还是继续暴力
不过要注意set的使用方法 删掉再进行暴力修改 再加回去
依旧不争气地继续用了STL
#include
#include
#include
#include
#include
using namespace std;
set<int> tr[1100];//set相当于一颗树
int s[110000],tag[110000],pos[110000];
int n,N;
void add(int x,int y,int c)
{
for (int i=x;i<=min(pos[x]*N,y);i++)
{
tr[pos[x]].erase(s[i]);
s[i]+=c;
tr[pos[x]].insert(s[i]);
}
if (pos[x]!=pos[y])
{
for (int i=(pos[y]-1)*N+1;i<=y;i++)
{
tr[pos[y]].erase(s[i]);
s[i]+=c;
tr[pos[y]].insert(s[i]);
}
}
for (int i=pos[x]+1;i<pos[y];i++) tag[i]+=c;
}
int findqianqu(int x,int y,int c)
{
int ans=-1;
for (int i=x;i<=min(pos[x]*N,y);i++)
{
int sum=s[i]+tag[pos[x]];
if (sumif (pos[x]!=pos[y])
{
for (int i=(pos[y]-1)*N+1;i<=y;i++)
{
int sum=s[i]+tag[pos[y]];
if (sumfor (int i=pos[x]+1;i<pos[y];i++)
{
int t=c-tag[i];
set<int>::iterator k=tr[i].lower_bound(t); //定义一个k的指针 一开始指向t的后继
if (k==tr[i].begin()) continue;
k--;
ans=max(ans,*k+tag[i]);//加*代表的是k所指向的值
}
return ans;
}
int main()
{
scanf("%d",&n);N=sqrt(n);
for (int i=1;i<=n;i++) scanf("%d",&s[i]);
for (int i=1;i<=n;i++)
{
pos[i]=(i-1)/N+1;
tr[pos[i]].insert(s[i]);
}
for (int i=1;i<=n;i++)
{
int u,x,y,c;
scanf("%d%d%d%d",&u,&x,&y,&c);
if (u==0) add(x,y,c);
else if (u==1) printf("%d\n",findqianqu(x,y,c));
}
return 0;
}
https://loj.ac/problem/6280
区间加法,区间求和
和分块1是差不多的
顺着思路用多一个数组来记录每一个块的总和就可以
#include
#include
#include
#include
using namespace std;
int n,N,pos[51000];
long long tag[51000],s[51000],a[51000]; //a:记录块内总和
void add(int x,int y,int c)
{
for (int i=x;i<=min(pos[x]*N,y);i++)
{
s[i]+=c;
a[pos[x]]+=c;
}
if (pos[x]!=pos[y])
{
for (int i=(pos[y]-1)*N+1;i<=y;i++)
{
s[i]+=c;
a[pos[y]]+=c;
}
}
for (int i=pos[x]+1;i<pos[y];i++) tag[i]+=c;
}
long long getsum(int x,int y)
{
long long ans=0;
for (int i=x;i<=min(pos[x]*N,y);i++)
{
ans+=s[i]+tag[pos[x]];
}
if (pos[x]!=pos[y])
{
for (int i=(pos[y]-1)*N+1;i<=y;i++)
{
ans+=s[i]+tag[pos[y]];
}
}
for (int i=pos[x]+1;i<pos[y];i++) ans+=a[i]+tag[i]*N;
return ans;
}
int main()
{
scanf("%d",&n);N=sqrt(n);
for (int i=1;i<=n;i++) pos[i]=(i-1)/N+1;
for (int i=1;i<=n;i++) {scanf("%d",&s[i]);a[pos[i]]+=s[i];}
for (int i=1;i<=n;i++)
{
int u,x,y,c;
scanf("%d%d%d%d",&u,&x,&y,&c);
if (u==0) add(x,y,c);
else if (u==1) printf("%lld\n",getsum(x,y)%(c+1));
}
return 0;
}
https://loj.ac/problem/6281
区间开方,区间求和
其实不难发现就,即使是开方,操作量也不是很大
就拿最大值 2^31来说 开5次平方就已经小于2接近1了
所以当一个数已经小于等于1的时候 开方也没有什么意义了(向下取整不是0就是1 1^n=1 0^n=0)
同理,我们参照上面的思路开多一个数组标记这个块内的数是不是全都小于等于1了,是的话直接不用更新就ok
#include
#include
#include
#include
using namespace std;
int pos[510000];
long long s[510000],a[510000];
bool v[510000]; //标记块内是不是全都<=1
int n,N;
void solvesqrt(int x)
{
if (v[x]) return ;
v[x]=1;a[x]=0;
for (int i=(x-1)*N+1;i<=x*N;i++)
{
s[i]=sqrt(s[i]);
a[x]+=s[i];
if (s[i]>1) v[x]=false;
}
}
void add(int x,int y)
{
if (v[pos[x]]==0)
{
for (int i=x;i<=min(pos[x]*N,y);i++)
{
a[pos[x]]-=s[i];
s[i]=sqrt(s[i]);
a[pos[x]]+=s[i];
}
v[pos[x]]=true;
for (int i=(pos[x]-1)*N+1;i<=pos[x]*N;i++)
if (s[i]>1) {v[pos[x]]=false;break;}
}
if (pos[x]!=pos[y]&&v[pos[y]]==0)
{
for (int i=(pos[y]-1)*N+1;i<=y;i++)
{
a[pos[y]]-=s[i];
s[i]=sqrt(s[i]);
a[pos[y]]+=s[i];
}
v[pos[y]]=true;
for (int i=(pos[y]-1)*N+1;i<=pos[y]*N;i++)
if (s[i]>1) {v[pos[y]]=false;break;}
}
for (int i=pos[x]+1;i<pos[y];i++) solvesqrt(i);
}
long long getsum(int x,int y)
{
long long ans=0;
for (int i=x;i<=min(pos[x]*N,y);i++) ans+=s[i];
if (pos[x]!=pos[y])
for (int i=(pos[y]-1)*N+1;i<=y;i++) ans+=s[i];
for (int i=pos[x]+1;i<pos[y];i++) ans+=a[i];
return ans;
}
int main()
{
memset(v,false,sizeof(v));
scanf("%d",&n);N=sqrt(n);
for (int i=1;i<=n;i++) scanf("%lld",&s[i]);
for (int i=1;i<=n;i++)
{
pos[i]=(i-1)/N+1;
a[pos[i]]+=s[i];
}
for (int i=1;i<=n;i++)
{
int u,x,y,c;
scanf("%d%d%d%d",&u,&x,&y,&c);
if (x>y) swap(x,y);
if (u==0) add(x,y);
else if (u==1) printf("%lld\n",getsum(x,y));
}
return 0;
}
这里顺便插入一个 bzoj3211: 花神游历各国&上帝造题的七分钟
如果不想写线段树的话可以写分块 其实就是分块5原题
刷一刷双经题
https://loj.ac/problem/6282
单点插入,单点询问
hzwer的代码太高深看不懂……
于是写了这个模样的
用a[i][j]记录第i块的第j个数
len[i]记录第i块有多少个数
修改的时候直接暴力找到点应在的块
暴力把r后面所有的往后挪
但是如果数据不随机 有可能点会集中的插在某个块当中,然后查询的时候直接……
所以当一个块的大小太大的时候就重新分一下块 (也可以直接把大的那个块拆成两个)
先用b数组分好在倒到a里面去
有没有觉得很巧妙很暴力
#include
#include
#include
#include
using namespace std;
int a[1100][1100],b[1100][1100],len[1100]; //a,b:相当于两个酱油瓶倒一倒 len:每一块的长度
int n,N,np=0,m;//np:new point计数器 新增点数数量, m:新总长
void rebuild()
{
int x=0,k=m/N,ll=0;
if (m%N!=0) k++;
m+=N; np=0;
N=sqrt(m);
for (int i=1;i<=k;i++)
{
for (int j=1;j<=len[i];j++)
{
x++;
int kk=(x-1)/N+1;
b[kk][++ll]=a[i][j];
a[i][j]=0;
if (ll>=N) ll=0;
}
}
k=m/N;
if (m%N==0) k++;
for (int i=1;ifor (int j=1;j<=len[i];j++) a[i][j]=b[i][j];
}
if (m%N==0) len[k]=N;
else len[k]=m-N*N;
for (int j=1;j<=len[k];j++) a[k][j]=b[k][j];
}
int main()
{
scanf("%d",&n);N=sqrt(n); m=n;
for (int i=1;i<=n;i++)
{
int s;
scanf("%d",&s);
int k=(i-1)/N+1;
a[k][++len[k]]=s;
}
for (int i=1;i<=n;i++)
{
int u,l,r,c;
scanf("%d%d%d%d",&u,&l,&r,&c);
if (u==0)
{
np++;
int x=0,t;
for (t=1;t<=N;t++)
{
if (x+len[t]>=l) break;
else x+=len[t];
}
len[t]++;
l=l-x;
for (int i=len[t];i>l;i--) a[t][i]=a[t][i-1];
a[t][l]=r;
if (np>=N) rebuild();//如果大于N就重新分一次块
}
else
{
int t,x=0;
for (t=1;t<=N;t++)
{
if (x+len[t]>=r) break;
else x+=len[t];
}
printf("%d\n",a[t][r-x]);
}
}
return 0;
}
https://loj.ac/problem/6283
区间乘法,区间加法,区间查询
其实和第二题并没有什么本质区别……
维护一个加法标记再维护一个乘法标记就ok
但是注意维护两个标记的时候要麻烦一点
其实记住运用好小学知识——乘法分配率 就ok
taga(加法标记)tagc(乘法标记)
先加再乘:taga=taga*c——tagc*=c
先乘再加:taga+=c ——tagc=tagc
暴力更新不完整的块的时候要先把之前整个块的两个标记放下去,再进行这一次的操作
还有记得初始化乘法标记不要写0……
代码写的很丑劳烦大佬看的时候屏蔽一下众多的%10007
#include
#include
#include
#include
using namespace std;
int pos[110000],s[110000],taga[110000],tagc[110000]; //taga:加法标记 tagc:乘法标记
int n,N;
void add(int x,int y,int c)
{
for (int i=(pos[x]-1)*N+1;i<=pos[x]*N;i++)
{
s[i]=(s[i]*tagc[pos[x]]%10007+taga[pos[x]]+10007)%10007;
if (x<=i&&i<=y) s[i]=(s[i]+c+10007)%10007;
}
tagc[pos[x]]=1;
taga[pos[x]]=0;
if (pos[x]!=pos[y])
{
for (int i=(pos[y]-1)*N+1;i<=pos[y]*N;i++)
{
s[i]=(s[i]*tagc[pos[y]]%10007+taga[pos[y]]+10007)%10007;
if (i<=y) s[i]=(s[i]+c+10007)%10007;
}
tagc[pos[y]]=1;
taga[pos[y]]=0;
}
for (int i=pos[x]+1;i<pos[y];i++) taga[i]=(taga[i]+c+10007)%10007;
}
void cf(int x,int y,int c)
{
for (int i=(pos[x]-1)*N+1;i<=pos[x]*N;i++)
{
s[i]=(s[i]*tagc[pos[x]]%10007+taga[pos[x]]+10007)%10007;
if (x<=i&&i<=y) s[i]=(s[i]*c+10007)%10007;
}
tagc[pos[x]]=1;
taga[pos[x]]=0;
if (pos[x]!=pos[y])
{
for (int i=(pos[y]-1)*N+1;i<=pos[y]*N;i++)
{
s[i]=(s[i]*tagc[pos[y]]%10007+taga[pos[y]]+10007)%10007;
if (i<=y) s[i]=(s[i]*c+10007)%10007;
}
tagc[pos[y]]=1;
taga[pos[y]]=0;
}
for (int i=pos[x]+1;i<pos[y];i++) {tagc[i]=(tagc[i]*c+10007)%10007;taga[i]=(taga[i]*c+10007)%10007;}
}
int main()
{
scanf("%d",&n);N=sqrt(n);
for (int i=1;i<=n;i++)
{
scanf("%d",&s[i]);
s[i]%=10007;
pos[i]=(i-1)/N+1;
taga[pos[i]]=0;tagc[pos[i]]=1;
}
for (int i=1;i<=n;i++)
{
int u,l,r,c;
scanf("%d%d%d%d",&u,&l,&r,&c);
c%=10007;
if (u==0) add(l,r,c);
else if (u==1) cf(l,r,c);
else if (u==2) printf("%d\n",(s[r]*tagc[pos[r]]+taga[pos[r]])%10007);
}
return 0;
}
https://loj.ac/problem/6284
区间询问,区间修改
继续运用上面的思路用上各种各样的标记就凹k
用一个数组记录这一个块内是否都是同一个数
是的话标记为这个数,否则为-1
查询的时候分情况讨论标记是否为-1 、c、≠c&&≠ -1
-1直接暴力for一遍统计然后更新标记
≠ -1&&≠c 没得询问直接更新
==c ans+=N 不用询问直接更新答案就好
#include
#include
#include
#include
using namespace std;
int pos[110000],a[110000],v[110000];
int n,N;
void solve(int x,int y,int c)
{
int ans=0;
if (pos[x]==pos[y])
{
if (v[pos[x]]==c) ans+=y-x+1;
else
{
if (v[pos[x]]!=-1)
{
for (int i=(pos[x]-1)*N+1;i<=pos[x]*N;i++) a[i]=v[pos[x]];
}
for (int i=x;i<=y;i++) if (a[i]==c) ans++; else a[i]=c;
bool bk=false;
for (int i=(pos[x]-1)*N+1;i<=pos[x]*N;i++) if (a[i]!=c) {bk=true;break;}
if (bk) v[pos[x]]=-1;
else v[pos[x]]=c;
}
}
else
{
if (v[pos[x]]==c) ans+=pos[x]*N-x+1;
else
{
if (v[pos[x]]!=-1)
for (int i=(pos[x]-1)*N+1;i<=pos[x]*N;i++) a[i]=v[pos[x]];
for (int i=x;i<=pos[x]*N;i++) if (a[i]==c) ans++; else a[i]=c;
bool bk=false;
for (int i=(pos[x]-1)*N+1;i<=pos[x]*N;i++) if (a[i]!=c) {bk=true;break;}
if (bk) v[pos[x]]=-1;
else v[pos[x]]=c;
}
if (v[pos[y]]==c) ans+=y-((pos[y]-1)*N+1)+1;
else
{
if (v[pos[y]]!=-1)
for (int i=(pos[y]-1)*N+1;i<=pos[y]*N;i++) a[i]=v[pos[y]];
for (int i=(pos[y]-1)*N+1;i<=y;i++) if (a[i]==c) ans++; else a[i]=c;
bool bk=false;
for (int i=(pos[y]-1)*N+1;i<=pos[y]*N;i++) if (a[i]!=c) {bk=true;break;}
if (bk) v[pos[y]]=-1;
else v[pos[y]]=c;
}
for (int i=pos[x]+1;i<pos[y];i++)
{
if (v[i]==c) ans+=N;
else
{
if (v[i]!=-1)
{
//for (int j=(i-1)*N+1;j<=i*N;j++) a[j]=c; 小心不要手抽写了这句小心会t!
v[i]=c;
}
else
{
for (int j=(i-1)*N+1;j<=i*N;j++) if (a[j]==c) ans++; else a[j]=c;
bool bk=false;
for (int j=(i-1)*N+1;j<=i*N;j++) if (a[j]!=c) {bk=true;break;}
if (bk) v[i]=-1;
else v[i]=c;
}
}
}
}
printf("%d\n",ans);
}
int main()
{
scanf("%d",&n);N=sqrt(n);
for (int i=1;i<=n;i++) scanf("%d",&a[i]);
for (int i=1;i<=n;i++) pos[i]=(i-1)/N+1;
memset(v,-1,sizeof(v));
for (int i=1;i<=n;i++)
{
int x,y,c;
scanf("%d%d%d",&x,&y,&c);
solve(x,y,c);
}
return 0;
}
https://loj.ac/problem/6285
查询区间最小众数
害怕太大所以离散化重新强制遍了一个号
然后利用map表每个元素只能出现一次的特质来存一下每个数离散化后的编号
顺便用一波丑丑的vector统计一下每个数第一个出现的位置
然后dp一波把每一个完整块内的众数先预处理一下
然后还是像上面那样
不完整的块暴力查找
完整的块直接max一下就好啦
#include
#include
#include
#include
#include
#include
using namespace std;
int pos[110000],v[110000],cnt[110000],a[110000];
int id,n,N;
int f[510][510];//块i到j之间的最小众数的id
map<int,int> m;//记录每个数的id
vector<int> b[51000];//记录每个数出现的第一个位置
void dp(int x) {
memset(cnt,0,sizeof(cnt));
int maxx=0,ss=0; //最大的id及最大的值
for (int i=(x-1)*N+1; i<=n; i++) {
int cc=++cnt[a[i]]; //统计每个数出现的个数
if (cc>ss||cc==ss&&v[a[i]]int along(int l,int r,int x) //ask how long
{
int ans=upper_bound(b[x].begin(),b[x].end(),r)-lower_bound(b[x].begin(),b[x].end(),l);//求l到r在x块的长度(r的后继-l的前驱)
return ans;
}
int solve(int x,int y)
{
int ss=0,maxx=0;
//完整的块
maxx=f[pos[x]+1][pos[y]-1];
ss=along(x,y,maxx);
//不完整的块
for (int i=x;i<=min(pos[x]*N,y);i++)
{
int cc=along(x,y,a[i]);
if (cc>ss||cc==ss&&v[a[i]]if (pos[x]!=pos[y])
{
for (int i=(pos[y]-1)*N+1;i<=y;i++)
{
int cc=along(x,y,a[i]);
if (cc>ss||cc==ss&&v[a[i]]return maxx;
}
int main() {
scanf("%d",&n);
N=sqrt(n);
for (int i=1; i<=n; i++)
{
scanf("%d",&a[i]);
pos[i]=(i-1)/N+1;
if (m[a[i]]==0)
{
m[a[i]]=++id;//a[i]是第几个出现的
v[id]=a[i];
}
a[i]=m[a[i]];//强制重新编号
b[a[i]].push_back(i);//记录每一个出现的位置
}
for (int i=1; i<=pos[n]; i++) dp(i);
for (int i=1; i<=n; i++)
{
int x,y;
scanf("%d%d",&x,&y);
if (x>y) swap(x,y);
printf("%d\n",v[solve(x,y)]);
}
return 0;
}
总结:
其实分块和莫队一样都是看着很美妙写的时候更美妙其实非常暴力的的东西
因为代码量相对正常的数据结构来说少很多
所以在接下来的省选中可以尝试来骗一骗分
AK的大佬们请自动跳过上一句话
祝大家AK
完结撒花~
(终于肝完了累死我了)