數列分塊入門 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;
}