突然有了学习分块和莫队的兴趣,其实很早就想学了,哦,对了,我离散化一开始都不熟(fw的真实)
Problem243. 一个简单的整数问题2
给定一个长度为 N 的数列 A,以及 M 条指令,每条指令可能是以下两种之一:
C l r d,表示把 A[l],A[l+1],…,A[r] 都加上 d。
Q l r,表示询问数列中第 l∼r 个数的和。
对于每个询问,输出一个整数表示答案。
1≤N,M≤10^5,
|d|≤10000,
|A[i]|≤10^9
本题有三种解法:树状数组、线段树、分块 将整个序列分成 n 个 n 长度的块,块内暴力 Q ∗ n ,大段维护 n ∗ n 总的时间复杂度是 ( Q + N ) ∗ N 本题有三种解法:树状数组、线段树、分块\\ 将整个序列分成\sqrt{n}个\sqrt{n}长度的块,块内暴力Q*\sqrt{n},大段维护n*\sqrt{n}\\ 总的时间复杂度是(Q+N)*\sqrt{N} 本题有三种解法:树状数组、线段树、分块将整个序列分成n个n长度的块,块内暴力Q∗n,大段维护n∗n总的时间复杂度是(Q+N)∗N
a 数组存储原数组, a d d 数组存储表示整个第 i 段的增量 ( 如果整段间有一小部分有增量,那么不算 ) s u m 数组表示第 i 段的区间和, ( 无论是被整段修改了,还是段内有修改, s u m 都要改变 s u m 数组定义不同实现不同 预处理了 p o s l 和 p o s r 数组所以比 y 总那么写要快,但是代码长了好多 a数组存储原数组,add数组存储表示整个第i段的增量(如果整段间有一小部分有增量,那么不算)\\ sum数组表示第i段的区间和,(无论是被整段修改了,还是段内有修改,sum都要改变 sum数组定义不同实现不同\\ 预处理了posl和posr数组所以比y总那么写要快,但是代码长了好多 a数组存储原数组,add数组存储表示整个第i段的增量(如果整段间有一小部分有增量,那么不算)sum数组表示第i段的区间和,(无论是被整段修改了,还是段内有修改,sum都要改变sum数组定义不同实现不同预处理了posl和posr数组所以比y总那么写要快,但是代码长了好多
在挑战模式用了20min默写的,发现还是没法搞清楚怎么使用add,sum数组,逻辑还是不清楚,好fw
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int a[N],sum[N],add[N];
int n,m,len;
int posl[N],posr[N],cnt;
int ask(int x)
{
if(x%len==0)
return x/len;
return x/len+1;
}
void add(int l,int r,int d)
{
int pl=ask(l),pr=ask(r);
//第几段的编号
if(pl==pr)
{
for(int i=l;i<=r;i++)
a[i]+=d;
sum[i]+=(r-l+1)*d;
}
else
{
for(int i=pl+1;i<pr;i++)
add[i]+=d;
//对于大段维护的情况sum需要修改吗?
for(int i=l;i<=posr[pl];i++)
a[i]+=d;
sum[pl]+=(posr[pl]-l+1)*d;
for(int i=posl[pr];i<=r;i++)
a[i]+=d;
sum[pr]+=(r-posl[pr]+1)*d;
}
}
ll query(int l,int r)
{
int pl=ask(l),pr=ask(r);
ll sum=0;
if(pl==pr)//段间进行暴力求和
{
for(int i=pl;i<=pr;i++)
{
sum+=a[i];
}
return sum;
}
else
{
for(int i=pl+1;i<pr;i++)
{
sum+=a[i];
}
for(int i=l;i<=posr[pl];i++)
{
sum+=a[i];
}
}
}
int main()
{
cin>>n;
len=sqrt(n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
// 分块
for(int i=1;i+len<=n;i+=len)
{
posl[++cnt]=(i-1)*len+1;
posr[cnt]=i*len;
}
if(posr[cnt]<n)
{
posl[cnt+1]=posr[cnt]+1;
posr[++cnt]=n;
}
//预处理sum数组
int i;
for(int j=1;j<=cnt;j++)
{
for(int t=1,i=1;t<=len&&i<=n;t++,i++)
{
sum[j]+=a[i];
}
}
while(m--)
{
char *s;int l,r,d;
scanf("%s%d%d",s,&l,&r);
if(s[0]=='C')
{
scanf("%d",&d);
add(l,r,d);
}
else
{
printf("%lld\n",query(l,r));
}
}
}
像 D P 一样定义清楚数组的定义再做 像DP一样定义清楚数组的定义再做 像DP一样定义清楚数组的定义再做
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;
int a[N],add[N];
ll sum[N];
// sum[i]表示第i段的区间和,add[i]表示第i段的增量标记
int n,m,len;
int posl[N],posr[N],cnt;
//posl表示分块之后的第i段的左端点
int ask(int x)//表示原区间下标为x的分块的编号
{
if(x%len==0)
return x/len;
return x/len+1;
}
void Add(int l,int r,int d)
{
int pl=ask(l),pr=ask(r);
//第几段的编号
if(pl==pr)//段内修改
{
for(int i=l;i<=r;i++)
a[i]+=d;
sum[pl]+=(r-l+1)*d;
}
else
{
for(int i=pl+1;i<pr;i++)
{
add[i]+=d;
sum[i]+=d*len;
}
//对于大段维护的情况sum需要修改吗?
//按照定义来说是需要的
for(int i=l;i<=posr[pl];i++)
a[i]+=d;
sum[pl]+=(posr[pl]-l+1)*d;
for(int i=posl[pr];i<=r;i++)
a[i]+=d;
sum[pr]+=(r-posl[pr]+1)*d;
}
}
/*
修改是为了query
段内的求和[l,r]
a[l]+...a[r]+add[pl]*(r-l+1)
整段求和
A B C
B是整段
s+=sum[b]+add[B]*len
对于A和C
s+=不用sum了
所以时间节省在了sum身上
*/
ll query(int l,int r)
{
int pl=ask(l),pr=ask(r);
ll s=0;
if(pl==pr)//段间进行暴力求和
{
for(int i=l;i<=r;i++)
{
s+=a[i];
}
s+=add[pl]*(r-l+1);
}
else
{
for(int i=pl+1;i<pr;i++)
{
s+=sum[i];
//+add[i]*len;
}
for(int i=l;i<=posr[pl];i++)
s+=a[i];
for(int i=posl[pr];i<=r;i++)
s+=a[i];
s+=add[pl]*(posr[pl]-l+1);
s+=add[pr]*(r-posl[pr]+1);
}
return s;
}
int main()
{
cin>>n>>m;
len=sqrt(n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
// 分块
for(int i=1;i*len<=n;i++)
{
posl[++cnt]=(i-1)*len+1;
posr[cnt]=i*len;
}
if(posr[cnt]<n)
{
posl[cnt+1]=posr[cnt]+1;
posr[++cnt]=n;
}
// for(int i=1;i<=cnt;i++)
// {
// cout<<posl[i]<<" "<<posr[i]<<endl;
// }
//预处理sum数组
int i=1;
for(int j=1;j<=cnt;j++)
{
for(int t=1;t<=len&&i<=n;t++,i++)
{
sum[j]+=a[i];
}
}
while(m--)
{
char s[3];int l,r,d;
scanf("%s%d%d",s,&l,&r);
if(s[0]=='C')
{
scanf("%d",&d);
Add(l,r,d);
}
else
{
printf("%lld\n",query(l,r));
}
}
}
上边代码稍微改动一点也能 a c 定义 a d d [ i ] 表示本段所有的数字都要加 a d d s u m [ i ] 表示第 i 段的真实和 ( 不算 a d d ) 修改之后的 修改是为了 q u e r y 段内的求和 [ l , r ] a [ l ] + . . . a [ r ] + a d d [ p l ] ∗ ( r − l + 1 ) 整段求和 [ l , r ] A B C B 是整段 s + = s u m [ b ] + a d d [ B ] ∗ l e n 对于 A 和 C s + = 不用 s u m 了 , 而是和段内暴力求和一样 所以时间节省在了 s u m 身上 上边代码稍微改动一点也能ac\\ 定义add[i]表示本段所有的数字都要加add\\ sum[i]表示第i段的真实和(不算add)\\ 修改之后的\\ 修改是为了query\\ 段内的求和[l,r]\\ a[l]+...a[r]+add[pl]*(r-l+1)\\ 整段求和[l,r]\\ A\,B\,C\\ B是整段s+=sum[b]+add[B]*len\\ 对于A和C\\ s+=不用sum了,而是和段内暴力求和一样\\ 所以时间节省在了sum身上 上边代码稍微改动一点也能ac定义add[i]表示本段所有的数字都要加addsum[i]表示第i段的真实和(不算add)修改之后的修改是为了query段内的求和[l,r]a[l]+...a[r]+add[pl]∗(r−l+1)整段求和[l,r]ABCB是整段s+=sum[b]+add[B]∗len对于A和Cs+=不用sum了,而是和段内暴力求和一样所以时间节省在了sum身上
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;
int a[N],add[N];
ll sum[N];
// sum[i]表示第i段的区间和,add[i]表示第i段的增量标记
int n,m,len;
int posl[N],posr[N],cnt;
//posl表示分块之后的第i段的左端点
int ask(int x)//表示原区间下标为x的分块的编号
{
if(x%len==0)
return x/len;
return x/len+1;
}
void Add(int l,int r,int d)
{
int pl=ask(l),pr=ask(r);
//第几段的编号
if(pl==pr)//段内修改
{
for(int i=l;i<=r;i++)
a[i]+=d;
sum[pl]+=(r-l+1)*d;
}
else
{
for(int i=pl+1;i<pr;i++)
{
add[i]+=d;
}
//对于大段维护的情况sum需要修改吗?
//按照定义来说是需要的
for(int i=l;i<=posr[pl];i++)
a[i]+=d;
sum[pl]+=(posr[pl]-l+1)*d;
for(int i=posl[pr];i<=r;i++)
a[i]+=d;
sum[pr]+=(r-posl[pr]+1)*d;
}
}
ll query(int l,int r)
{
int pl=ask(l),pr=ask(r);
ll s=0;
if(pl==pr)//段间进行暴力求和
{
for(int i=l;i<=r;i++)
{
s+=a[i];
}
s+=add[pl]*(r-l+1);
}
else
{
for(int i=pl+1;i<pr;i++)
{
s+=sum[i]+add[i]*len;
}
for(int i=l;i<=posr[pl];i++)
s+=a[i];
for(int i=posl[pr];i<=r;i++)
s+=a[i];
s+=add[pl]*(posr[pl]-l+1);
s+=add[pr]*(r-posl[pr]+1);
}
return s;
}
int main()
{
cin>>n>>m;
len=sqrt(n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
// 分块
for(int i=1;i*len<=n;i++)
{
posl[++cnt]=(i-1)*len+1;
posr[cnt]=i*len;
}
if(posr[cnt]<n)
{
posl[cnt+1]=posr[cnt]+1;
posr[++cnt]=n;
}
// for(int i=1;i<=cnt;i++)
// {
// cout<<posl[i]<<" "<<posr[i]<<endl;
// }
//预处理sum数组
int i=1;
for(int j=1;j<=cnt;j++)
{
for(int t=1;t<=len&&i<=n;t++,i++)
{
sum[j]+=a[i];
}
}
while(m--)
{
char s[3];int l,r,d;
scanf("%s%d%d",s,&l,&r);
if(s[0]=='C')
{
scanf("%d",&d);
Add(l,r,d);
}
else
{
printf("%lld\n",query(l,r));
}
}
}
Problem947. 文本编辑器(块状链表)
听课笔记
分块,块之间用双向链表维护起来
先咕着