莫队与分块

突然有了学习分块和莫队的兴趣,其实很早就想学了,哦,对了,我离散化一开始都不熟(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 长度的块,块内暴力Qn ,大段维护nn 总的时间复杂度是(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数组定义不同实现不同预处理了poslposr数组所以比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](rl+1)整段求和[l,r]ABCB是整段s+=sum[b]+add[B]len对于ACs+=不用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. 文本编辑器(块状链表)


听课笔记
分块,块之间用双向链表维护起来
先咕着

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值