数列分块入门 1-8
数列分块入门 1-8(蒟蒻没写9)
数列分块入门 1
题意是区间修改单点查询,运用分块思想,在区间里是一整块的直接打标记,零散的直接加,在查询的时候返回当前点的值加上它所属的块的加法标记即可
代码
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
const int maxn = 5e4+10;
int delta[maxn],a[maxn],bl[maxn];
int block;
void modify(int l,int r,int val){
for(int i=l;i<=min(r,bl[l] * block);++i){
a[i] += val;
}
if(bl[l] != bl[r]){
for(int i=(bl[r] - 1) * block + 1;i <= r; ++i){
a[i] += val;
}
}
for(int i=bl[l] + 1;i <= bl[r] - 1;++i){
delta[i] += val;
}
}
int main(){
int n;
scanf("%d",&n);
block = sqrt(n);
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);
bl[i] = (i-1)/block + 1;
}
for(int i=1;i<=n;++i){
int opt,l,r,val;
scanf("%d%d%d%d",&opt,&l,&r,&val);
if(opt){
printf("%d\n",a[r] + delta[bl[r]]);
}
else{
modify(l,r,val);
}
}
return 0;
}
数列分块入门 2
题意就是区间修改然后找区间内小于某个值的个数。
区间修改跟上一次的一样,打标记,因为要查询每一次比当前值小的有多少个,所以我们把每一块的值都放到一个 \(vector\) 中,并且排序,在对分散的点修改值完后,需要重新排序,查询的时候散点直接加上标记然后比较即可,整块的就记录一下要查的值减去当前块的标记,然后二分即可。
代码
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
#include<cmath>
using namespace std;
const int maxn = 5e4+10;
vector<int> g[maxn];
int a[maxn],bl[maxn],delta[maxn];
int n;
int block;
void resort(int k){
g[k].clear();
for(int i = (k-1) * block + 1;i <= min(n,k * block);++i){
g[k].push_back(a[i]);
}
sort(g[k].begin(),g[k].end());
}
void modify(int l,int r,int val){
for(int i=l;i<=min(bl[l] * block,r);++i){
a[i]+=val;
}
resort(bl[l]);
if(bl[l] != bl[r]){
for(int i = (bl[r]-1) * block + 1;i <= r;++i){
a[i] += val;
}
resort(bl[r]);
}
for(int i=bl[l] + 1;i <= bl[r] - 1;++i){
delta[i] += val;
}
}
int query(int l,int r,int val){
int ans = 0;
for(int i=l;i<=min(bl[l] * block,r);++i){
if(a[i]+delta[bl[l]] < val)ans++;
}
if(bl[l] != bl[r]){
for(int i=(bl[r]-1) * block + 1;i<=r;++i){
if(a[i] + delta[bl[r]] < val)ans++;
}
}
for(int i=bl[l]+1;i<=bl[r]-1;++i){
int x = val - delta[i];
ans += lower_bound(g[i].begin(),g[i].end(),x) - g[i].begin();
}
return ans;
}
int main(){
scanf("%d",&n);
block = sqrt(n);
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);
bl[i] = (i-1)/block + 1;
g[bl[i]].push_back(a[i]);
}
for(int i=1;i<=bl[n];++i){
sort(g[i].begin(),g[i].end());
}
for(int i=1;i<=n;++i){
int l,r,opt,val;
scanf("%d%d%d%d",&opt,&l,&r,&val);
if(opt == 0){
modify(l,r,val);
}
else{
printf("%d\n",query(l,r,val * val));
}
}
return 0;
}
数列分块入门 3
区间加法并查找前驱,跟上边一样用 \(vector\) 记录并排序,每次更新重新排序,在查询的时候散点就找最大的值加上标记,整块的就是二分,二分到的位置前一个就是。
代码
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
#include<cmath>
using namespace std;
const int maxn = 1e5+10;
vector<int> g[maxn];
int a[maxn],bl[maxn],delta[maxn];
int n;
int block;
void resort(int k){
g[k].clear();
for(int i = (k-1) * block + 1;i <= min(n,k * block);++i){
g[k].push_back(a[i]);
}
sort(g[k].begin(),g[k].end());
}
void modify(int l,int r,int val){
for(int i=l;i<=min(bl[l] * block,r);++i){
a[i]+=val;
}
resort(bl[l]);
if(bl[l] != bl[r]){
for(int i = (bl[r]-1) * block + 1;i <= r;++i){
a[i] += val;
}
resort(bl[r]);
}
for(int i=bl[l] + 1;i <= bl[r] - 1;++i){
delta[i] += val;
}
}
int query(int l,int r,int val){
int ans = -1;
for(int i=l;i<=min(bl[l] * block,r);++i){
if(a[i]+delta[bl[l]] < val){
ans = max(ans,a[i]+delta[bl[l]]);
}
}
if(bl[l] != bl[r]){
for(int i=(bl[r]-1) * block + 1;i<=r;++i){
if(a[i] + delta[bl[r]] < val){
ans = max(ans,a[i] + delta[bl[r]]);
}
}
}
for(int i=bl[l]+1;i<=bl[r]-1;++i){
int x = lower_bound(g[i].begin(),g[i].end(),val - delta[i]) - g[i].begin();
if(x){
ans = max(ans,g[i][x-1] + delta[i]);
}
}
return ans;
}
int main(){
scanf("%d",&n);
block = sqrt(n);
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);
bl[i] = (i-1)/block + 1;
g[bl[i]].push_back(a[i]);
}
for(int i=1;i<=bl[n];++i){
sort(g[i].begin(),g[i].end());
}
for(int i=1;i<=n;++i){
int l,r,opt,val;
scanf("%d%d%d%d",&opt,&l,&r,&val);
if(opt == 0){
modify(l,r,val);
}
else{
printf("%d\n",query(l,r,val));
}
}
return 0;
}
数列分块入门 4
区间查询并区间修改。
区间修改跟上边大同小异,但是需要记录每一个块加上了多少(包括散点),在查询的时候散点正常加上标记,整块的就加上记录下来的那个每一块加上了多少即可。
代码
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
#include<cmath>
using namespace std;
#define int long long
const int maxn = 5e4+10;
int a[maxn],bl[maxn],delta[maxn];
int n;
int jl[maxn];
int block;
void modify(int l,int r,int val){
for(int i=l;i<=min(bl[l] * block,r);++i){
a[i]+=val;
jl[bl[l]] += val;
}
if(bl[l] != bl[r]){
for(int i = (bl[r]-1) * block + 1;i <= r;++i){
a[i] += val;
jl[bl[r]] += val;
}
}
for(int i=bl[l] + 1;i <= bl[r] - 1;++i){
delta[i] += val;
jl[i] += val * block;
}
}
int query(int l,int r,int mod){
int ans = 0;
mod++;
for(int i=l;i<=min(bl[l] * block,r);++i){
ans += (a[i] + delta[bl[l]]) % mod;
ans %= mod;
}
if(bl[l] != bl[r]){
for(int i = (bl[r]-1) * block + 1;i <= r;++i){
ans += (a[i] + delta[bl[r]]) % mod;
ans %= mod;
}
}
for(int i=bl[l] + 1;i <= bl[r] - 1;++i){
ans = (ans + jl[i]) % mod;
}
return ans % mod;
}
signed main(){
scanf("%lld",&n);
block = sqrt(n);
for(int i=1;i<=n;++i){
scanf("%lld",&a[i]);
bl[i] = (i-1)/block + 1;
jl[bl[i]] += a[i];
}
for(int i=1;i<=n;++i){
int l,r,opt,val;
scanf("%lld%lld%lld%lld",&opt,&l,&r,&val);
if(opt == 0){
modify(l,r,val);
}
else{
printf("%lld\n",query(l,r,val));
}
}
return 0;
}
数列分块入门 5
区间开方 + 区间求和。
我们用一个记录数组记录每个块的值,在进行散点的修改时先减去当前点值,开根号后再加上。
需要注意的是,当一个数是 \(1\) 或 \(0\) 的时候就不用再开方了,所以在整块修改的时候我们先置空当前块的记录值,然后依次加上开根后的值,判断一下是否有 \(0\) 和 \(1\) ,如果没有就标记为 \(0\) ,当标记为 \(1\) 时,当前块不需要开根,查询的时候单点单加,整块的就直接加上记录的值即可。
代码
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
#include<cmath>
using namespace std;
#define int long long
const int maxn = 5e4+10;
int a[maxn],bl[maxn],delta[maxn];
int n;
int jl[maxn];
bool flag[maxn];
int block;
void change(int id){
jl[id] = 0;
bool jud = 1;
for(int i=(id-1) * block + 1; i<=id * block; ++i){
a[i] = sqrt(a[i]);
jl[id] += a[i];
if(a[i] != 1 && a[i] != 0){
jud = 0;
}
}
flag[id] = jud;
}
void modify(int l,int r){
for(int i=l;i<=min(bl[l] * block,r);++i){
jl[bl[l]] -= a[i];
a[i] = sqrt(a[i]);
jl[bl[l]] += a[i];
}
if(bl[l] != bl[r]){
for(int i = (bl[r]-1) * block + 1;i <= r;++i){
jl[bl[r]] -= a[i];
a[i] = sqrt(a[i]);
jl[bl[r]] += a[i];
}
}
for(int i=bl[l] + 1;i <= bl[r] - 1;++i){
if(flag[i])continue;
change(i);
}
}
int query(int l,int r){
int ans = 0;
for(int i=l;i<=min(bl[l] * block,r);++i){
ans += a[i];
}
if(bl[l] != bl[r]){
for(int i = (bl[r]-1) * block + 1;i <= r;++i){
ans += a[i];
}
}
for(int i=bl[l] + 1;i <= bl[r] - 1;++i){
ans += jl[i];
}
return ans;
}
signed main(){
scanf("%lld",&n);
block = sqrt(n);
for(int i=1;i<=n;++i){
scanf("%lld",&a[i]);
bl[i] = (i-1)/block + 1;
jl[bl[i]] += a[i];
}
for(int i=1;i<=n;++i){
int l,r,opt,val;
scanf("%lld%lld%lld%lld",&opt,&l,&r,&val);
if(opt == 0){
modify(l,r);
}
else{
printf("%lld\n",query(l,r));
}
}
return 0;
}
数列分块入门 6
单点插入,单点询问。
看到插入我们很自然的应该就能想到用 \(vector\) ,因为他的插入操作是最便捷的。
插入的时候依次枚举块,逐渐把要插入的位置减去块的大小,最终就是要插入的位置,直接插入。
查询的时候跟插入一样,依次减去块大小,最后找到 \(pos-1\) 的值就行。
代码
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
const int L=1<<20;
char buffer[L],*S,*T;
#define lowbit(x) (x & -x)
#define getchar() (S==T&&(T=(S=buffer)+fread(buffer,1,L,stdin),S==T)?EOF:*S++)
#define inline __inline__ __attribute__((__always_inline__))
#define max(a,b) (a>b?a:b)
#define re register
const int maxn = 1e5+10;
struct Node{
int x,y,val;
}e[maxn+200000];
int bl[maxn],block;
vector<int>g[maxn];
inline int read(){
int s = 0,f = 1;
char ch = getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch))s = s * 10 + ch - '0',ch = getchar();
return s * f;
}
inline void insert(int pos,int val){
int now = 1;
while(pos > g[now].size()){
pos -= g[now].size();
now++;
}
g[now].insert(g[now].begin()+pos,val);
}
inline int query(int pos){
int now = 1;
while(pos > g[now].size()){
pos -= g[now].size();
now++;
}
return g[now][pos-1];
}
int main(){
re int n = read();
block = sqrt(n);
for(re int i=1;i<=n;++i){
bl[i] = (i-1)/block + 1;
re int x = read();
g[bl[i]].push_back(x);
}
for(re int i = 1;i<=n;++i){
int opt = read(),l = read(),r = read(),val = read();
if(opt == 0){
insert(l-1,r);
}
else{
printf("%d\n",query(r));
}
}
return 0;
}
数列分块入门 7
区间乘法 + 加法 + 单点查询。
其实跟其他的没什么区别,只是加一个乘法标记,在每一次修改的时候都要标记下放,查询的时候先乘后加即可。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<vector>
using namespace std;
const int L=1<<20;
char buffer[L],*S,*T;
#define getchar() (S==T&&(T=(S=buffer)+fread(buffer,1,L,stdin),S==T)?EOF:*S++)
#define inline __inline__ __attribute__((__always_inline__))
#define re register
#define int long long
const int maxn = 1e6+10;
const int mod = 1e4+7;
struct Node{
int x,y,val;
}e[maxn];
int a[maxn];
int bl[maxn],block;
int add[maxn],mul[maxn];
int n;
inline int read(){
int s = 0,f = 1;
char ch = getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch))s = s * 10 + ch - '0',ch = getchar();
return s * f;
}
inline void pushdown(int id){
for(int i = (id - 1) * block + 1;i <= id * block;++i){
a[i] = (a[i] * mul[bl[i]] % mod + add[bl[i]] % mod) % mod;
}
add[id] = 0;
mul[id] = 1;
}
inline void modify(int l,int r,int val){
pushdown(bl[l]);
for(int i = l;i <= min(bl[l] * block,r);++i){
a[i] = (a[i] + val) % mod;
}
if(bl[l] != bl[r]){
pushdown(bl[r]);
for(int i = (bl[r] - 1) * block + 1 ;i <= r;++i){
a[i] = (a[i] + val) % mod;
}
}
for(int i=bl[l] + 1;i <= bl[r] - 1;++i){
add[i] = (add[i] + val) % mod;
}
}
inline void multiply(int l,int r,int val){
pushdown(bl[l]);
for(int i = l;i <= min(bl[l] * block,r);++i){
a[i] = (a[i] * val) % mod;
}
if(bl[l] != bl[r]){
pushdown(bl[r]);
for(int i = (bl[r] - 1) * block + 1; i <= r;++i){
a[i] = (a[i] * val) % mod;
}
}
for(int i=bl[l] + 1;i <= bl[r] - 1;++i){
add[i] = (add[i] * val) % mod;
mul[i] = (mul[i] * val) % mod;
}
}
inline int query(int pos){
return (a[pos] * mul[bl[pos]] % mod + add[bl[pos]] % mod) % mod;
}
signed main(){
n = read();
block = sqrt(n);
for(int i=1;i<=n;++i){
a[i] = read();
bl[i] = (i-1)/block + 1;
mul[bl[i]] = 1;
}
for (int i = 1; i <= n; ++i) {
int t, x, y, z;
t = read(),x = read(),y = read(),z = read();
if (t == 0) {
modify(x, y, z);
} else if (t == 1) {
multiply(x, y, z);
} else {
printf("%lld\n", query(y));
}
}
return 0;
}
数列分块入门 8
询问等于一个数 \(c\) 的元素,并将这个区间的所有元素改为 \(c\) 。
散点直接修改并查询,设置一个 \(tag\) 标记,先下放,整块修改的话,如果当前的标记有值,且不等于 \(c\) ,那么直接给标记赋值,统计答案。
如果标记没值,那么扫一遍当前块,直接赋值(同散点),最后标记记录 \(c\)。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<vector>
using namespace std;
const int L=1<<20;
char buffer[L],*S,*T;
#define getchar() (S==T&&(T=(S=buffer)+fread(buffer,1,L,stdin),S==T)?EOF:*S++)
#define inline __inline__ __attribute__((__always_inline__))
#define re register
const int maxn = 1e6+10;
int tag[maxn],a[maxn],bl[maxn],block;
int n;
inline int read(){
int s = 0,f = 1;
char ch = getchar();
while(!isdigit(ch)){
if(ch == '-')f = -1;
ch = getchar();
}
while(isdigit(ch)){
s = s * 10 + ch - '0';
ch = getchar();
}
return s * f;
}
inline void pushdown(int id){
if(tag[id] == -1)return;
for(re int i = (id - 1) * block + 1; i <= id * block; ++i){
a[i] = tag[id];
}
tag[id] = -1;
}
inline int modify(int l,int r,int c){
pushdown(bl[l]);
re int ans = 0;
for(re int i = l;i <= min(bl[l] * block,r);++i){
if(a[i] != c){
a[i] = c;
}
else ans++;
}
if(bl[l] != bl[r]){
pushdown(bl[r]);
for(re int i = (bl[r] - 1) * block + 1; i <= r;++i){
if(a[i] != c){
a[i] = c;
}
else ans++;
}
}
for(re int i = bl[l] + 1;i <= bl[r] - 1;++i){
if(tag[i] != -1){
if(tag[i] != c){
tag[i] = c;
}
else ans += block;
}
else{
for(re int j = (i-1) * block + 1;j <= i * block; ++j){
if(a[j] != c){
a[j] = c;
}
else ans++;
}
tag[i] = c;
}
}
return ans;
}
int main(){
memset(tag,-1,sizeof(tag));
n = read();
block = sqrt(n);
for(re int i = 1;i<=n;++i){
a[i] = read();
bl[i] = (i-1)/block + 1;
}
for(re int i = 1;i <= n;++i){
re int l = read(),r = read(),c = read();
printf("%d\n",modify(l,r,c));
}
return 0;
}