算法 - 分块

  • 分块的基本思想是,将处理的一组数分成一些块,对于每个l-r的操作,l和r两端所在的不完整的块单独处理,其余每个完整块只需统计这个块的信息而不必遍历这个块内所有的元素,从而节约时间。
  • 分块大小通常可以使用根号n,但对于不同题目可以使用均值不等式找到更好的分块大小。
  • hzwer的分块教程
  • 题目列表
  • 简易题解
  • notice:我的代码元素个数都是0~n-1,块的编号也是。这样可以使用i/size找到i在第几个块,比较方便。
  • Pro1
  • a[i]表示第i个数的值,block[i]表示第i个块累加的值。
  • 每次加法,对于不是一个完整块的部分,累加a[i]的值。
  • 每次加法,对于是一个完整块的部分,累加block[i/size]的值。
  • 每次询问,输出a[i]+block[i/size]。
  • 代码
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
int a[51000],block[51000];
int main()
{
    memset(block,0,sizeof(block));
    int n,size;
    scanf("%d",&n);
    size=sqrt(n)+1;
    for(int i=0;i<n;i++)scanf("%d",&a[i]);
    for(int i=1;i<=n;i++)
    {
        int op,l,r,c;
        scanf("%d%d%d%d",&op,&l,&r,&c);
        l--;r--;
        if(op==0)
        {
            int now=l;
            for(;now%size!=0&&now<=r;now++)
                a[now]+=c;
            for(;now+size-1<=r;now+=size)
                block[now/size]+=c;
            for(;now<=r;now++)
                a[now]+=c;
        }
        else printf("%d\n",a[r]+block[r/size]);
    }
    return 0;
}
  • Pro2
  • a[i]表示第i个数的值
  • 随即将每一个完整块元素进行排序,放入b[i]。
  • block[i]同Pro1。
  • 每次加法,同Pro1,然后更新两端的不完整块的b[i]。
  • 每次询问,对于不完整的块,在a[i]中暴力统计;对于每一个完整的块,在b[i]中进行二分统计。
  • 代码
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#define inf (1e10)
using namespace std;
int cmp(const void *xx,const void *yy)
{
    int d1=*(int *)xx;
    int d2=*(int *)yy;
    if(d1>d2)return 1;
    if(d1==d2)return 0;
    return -1;
}
int a[51000],b[51000],block[51000];
int main()
{
    //freopen("fk.in","r",stdin);
    //freopen("ans_mine.out","w",stdout);
    memset(a,63,sizeof(a));
    memset(b,63,sizeof(b));
    int n,size;
    scanf("%d",&n);
    size=sqrt(n)+1;
    for(int i=0;i<n;i++)
    {
        scanf("%d",&a[i]);
        b[i]=a[i];
    }
    for(int i=0;i<n;i+=size)
        qsort(b+i,size,sizeof(int),cmp);
    for(int t=1;t<=n;t++)
    {
        int op,l,r,c;
        scanf("%d%d%d%d",&op,&l,&r,&c);
        l--;r--;
        if(op==0)
        {
            bool change=false;
            int now=l;
            for(;now%size!=0&&now<=r;now++)
            {
                a[now]+=c;
                change=true;
            }
            if(change==true)
            {
                int x=(now-1)/size*size;
                for(int i=0;i<size;i++)
                    b[x+i]=a[x+i];
                qsort(b+x,size,sizeof(int),cmp);
            }
            for(;now+size-1<=r;now+=size)
                block[now/size]+=c;
            change=false;
            for(;now<=r;now++)
            {
                a[now]+=c;
                change=true;
            }
            if(change==true)
            {
                int x=r/size*size;
                for(int i=0;i<size;i++)
                    b[x+i]=a[x+i];
                qsort(b+x,size,sizeof(int),cmp);
            }
        }
        else
        {
            int now=l,ans=0,val=c*c;
            for(;now%size!=0&&now<=r;now++)
                if(a[now]+block[now/size]<val)
                    ans++;
            for(;now+size-1<=r;now+=size)
            {
                int l=now,r=now+size-1,add=now-1;
                int mid=(l+r)/2;
                while(l<=r)
                {
                    if(b[mid]+block[mid/size]<val)
                    {
                        add=mid;
                        l=mid+1;
                    }else r=mid-1;
                    mid=(l+r)/2;
                }
                ans+=(add-now+1);
            }
            for(;now<=r;now++)
                if(a[now]+block[now/size]<val)
                    ans++;
            printf("%d\n",ans);
        }   
    }
    return 0;
}
  • Pro3
  • 和Pro2并没有实质性的区别。
  • 代码:
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#define inf (1e10)
using namespace std;
int cmp(const void *xx,const void *yy)
{
    int d1=*(int *)xx;
    int d2=*(int *)yy;
    if(d1>d2)return 1;
    if(d1==d2)return 0;
    return -1;
}
int a[110000],b[110000],block[110000];
int main()
{
    memset(a,63,sizeof(a));
    memset(b,63,sizeof(b));
    int n,size;
    scanf("%d",&n);
    size=sqrt(n)+1;
    for(int i=0;i<n;i++)
    {
        scanf("%d",&a[i]);
        b[i]=a[i];
    }
    for(int i=0;i<n;i+=size)
        qsort(b+i,size,sizeof(int),cmp);
    for(int t=1;t<=n;t++)
    {
        int op,l,r,c;
        scanf("%d%d%d%d",&op,&l,&r,&c);
        l--;r--;
        if(op==0)
        {
            bool change=false;
            int now=l;
            for(;now%size!=0&&now<=r;now++)
            {
                a[now]+=c;
                change=true;
            }
            if(change==true)
            {
                int x=(now-1)/size*size;
                for(int i=0;i<size;i++)
                    b[x+i]=a[x+i];
                qsort(b+x,size,sizeof(int),cmp);
            }
            for(;now+size-1<=r;now+=size)
                block[now/size]+=c;
            change=false;
            for(;now<=r;now++)
            {
                a[now]+=c;
                change=true;
            }
            if(change==true)
            {
                int x=r/size*size;
                for(int i=0;i<size;i++)
                    b[x+i]=a[x+i];
                qsort(b+x,size,sizeof(int),cmp);
            }
        }
        else
        {
            int now=l,ans=-1;
            for(;now%size!=0&&now<=r;now++)
                if(a[now]+block[now/size]<c&&a[now]+block[now/size]>ans)
                    ans=a[now]+block[now/size];
            for(;now+size-1<=r;now+=size)
            {
                int l=now,r=now+size-1;
                int mid=(l+r)/2;
                while(l<=r)
                {
                    if(b[mid]+block[mid/size]<c)
                    {
                        ans=max(ans,b[mid]+block[mid/size]);
                        l=mid+1;
                    }else r=mid-1;
                    mid=(l+r)/2;
                }
            }
            for(;now<=r;now++)
                if(a[now]+block[now/size]<c&&a[now]+block[now/size]>ans)
                    ans=a[now]+block[now/size];
            printf("%d\n",ans);
        }   
    }
    return 0;
}
  • Pro4
  • a[i]表示第i个数的值,block[i]表示第i个块累加的值,total[i]表示第i个块的元素和。
  • 每次加法,对于不是一个完整块的部分,累加a[i]的值,且为total[i/size]加上c。
  • 每次加法,对于是一个完整块的部分,累加block[i/size]的值,且为total[i/size]加上c*size。
  • 每次询问,ans累加两端不完整块的a[i]+block[i/size],累加完整块的total。
  • 代码:
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#define LL long long
using namespace std;
LL a[110000],block[110000],total[110000];
int main()
{
    memset(a,0,sizeof(a));
    memset(total,0,sizeof(total));
    int n,size;
    scanf("%d",&n);
    size=sqrt(n)+1;
    for(int i=0;i<n;i++)
    {
        scanf("%lld",&a[i]);
        total[i/size]+=a[i];
    }    
    for(int t=1;t<=n;t++)
    {
        int op,l,r;LL c;
        scanf("%d%d%d%lld",&op,&l,&r,&c);
        l--;r--;
        if(op==0)
        {
            int now=l;
            for(;now%size!=0&&now<=r;now++)
            {
                a[now]+=c;
                total[now/size]+=c;
            }
            for(;now+size-1<=r;now+=size)
            {
                block[now/size]+=c;
                total[now/size]+=c*size;
            }
            for(;now<=r;now++)
            {
                a[now]+=c;
                total[now/size]+=c;
            }
        }
        else
        {
            int now=l;
            LL ans=0;
            for(;now%size!=0&&now<=r;now++)
                ans=(ans+a[now]+block[now/size])%(c+1);
            for(;now+size-1<=r;now+=size)
                ans=(ans+total[now/size])%(c+1);
            for(;now<=r;now++)
                ans=(ans+a[now]+block[now/size])%(c+1);
            printf("%lld\n",ans);
        }   
    }
    return 0;
}
  • Pro5
  • 传说中的面向数据编程。
  • 所有数你开了几次方以后都变为0或1,所以使用block[i]表示这一块是否都变成了0或1。
  • 修改时block[i]=true块的跳过;
  • 修改时block[i]=false的块暴力修改且更新total值。
  • 询问时类似Pro4。
  • 代码:
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#define LL long long
using namespace std;
int a[51000];
LL total[51000];
bool block[51000];
int main()
{
    memset(total,0,sizeof(total));
    memset(block,false,sizeof(block));
    int n,size;
    scanf("%d",&n);
    size=sqrt(n)+1;
    for(int i=0;i<n;i++)
    {
        scanf("%d",&a[i]);
        total[i/size]+=a[i];
    }
    for(int t=1;t<=n;t++)
    {
        int op,l,r,c;
        scanf("%d%d%d%d",&op,&l,&r,&c);
        l--;r--;
        if(op==0)
        {
            int now=l;
            for(;now%size!=0&&now<=r;now++)
            {
                total[now/size]-=a[now];
                a[now]=sqrt(a[now]);
                total[now/size]+=a[now];
            }
            for(;now+size-1<=r;now+=size)
            {
                if(block[now/size])
                    continue;
                bool changesign=true;
                total[now/size]=0;
                for(int i=now;i<=now+size-1;i++)
                {
                    a[i]=sqrt(a[i]);
                    total[now/size]+=a[i];
                    if(a[i]!=0&&a[i]!=1)
                        changesign=false;
                }
                if(changesign==true)
                    block[now/size]=true;
            }
            for(;now<=r;now++)
            {
                total[now/size]-=a[now];
                a[now]=sqrt(a[now]);
                total[now/size]+=a[now];
            }
        }
        else
        {
            int now=l;
            LL ans=0;
            for(;now%size!=0&&now<=r;now++)
                ans+=a[now];
            for(;now+size-1<=r;now+=size)
                ans+=total[now/size];
            for(;now<=r;now++)
                ans+=a[now];
            printf("%lld\n",ans);
        }   
    }
    return 0;
}
  • Pro6
  • 我们将每个块的容量开到大于两倍size。
  • 插入时,暴力把该块中该插入点后的所有元素往后挪,然后插入元素。
  • 用num[i]记录第i个块的元素个数。
  • 查询时,每次加上遍历到的该块的num,然后看看是否大于要找的位置——也就是要找的元素在该块中的意思。
  • 但是这样有一个问题:如果疯狂往一个块内插入元素,那么数组就会爆掉。
  • 因此每size个插入操作,就把所有元素取出来,丢到list,重新分组。
  • 这题有个坑:插入会使得这个块元素个数变多,所以size只能在重构完还没加入新元素的时候使用,其他地方都不能使用size。
  • 有可能更快的方案:只是当一个块的元素达到2size时重新分组。我不确定哪个更快,因为这样虽然分组次数变少,但平均每个时间有更多的大块。
  • 代码:
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#define LL long long
using namespace std;
int a[1100][1100];
int num[110000];
int list[210000];
int main()
{
    //freopen("fk.in","r",stdin);
    //freopen("ans_mine.out","w",stdout);
    memset(a,0,sizeof(a));
    memset(num,0,sizeof(num));
    int n,stn,blockNum,size;
    scanf("%d",&n);stn=n;
    size=sqrt(n)+1;
    for(int i=0;i<n;i++)
    {
        scanf("%d",&a[i/size][i-(i/size*size)]);
        num[i/size]++;
    }
    blockNum=(n-1)/size+1;
    int insCnt=0;
    for(int t=1;t<=stn;t++)
    {
        int op,l,r,c;
        scanf("%d%d%d%d",&op,&l,&r,&c);
        if(op==0)
        {
            insCnt++;
            int now=0,nowBlock=-1;
            while(1)
            {
                nowBlock++;
                if(now+num[nowBlock]>=l)
                    break;
                now+=num[nowBlock];
            }
            l=l-now-1;
            for(int i=num[nowBlock]-1;i>=l;i--)
                a[nowBlock][i+1]=a[nowBlock][i];
            a[nowBlock][l]=r;
            num[nowBlock]++;
            n++;
            //重构 
            if(insCnt==size)
            {
                insCnt=0;
                int len=-1;
                for(int i=0;i<blockNum;i++)
                    for(int j=0;j<num[i];j++)
                        list[++len]=a[i][j];
                size=sqrt(n)+1;
                blockNum=(n-1)/size+1;
                memset(num,0,sizeof(num));
                for(int i=0;i<len;i++)
                {
                    a[i/size][i-(i/size*size)]=list[i];
                    num[i/size]++;
                }
            }  
        }
        else
        {
            int now=0,nowBlock=-1;
            while(1)
            {
                nowBlock++;
                if(now+num[nowBlock]>=r)
                    break;
                now+=num[nowBlock];
            }
            printf("%d\n",a[nowBlock][r-now-1]);
        }   
    }
    return 0;
}
  • Pro7
  • 我们使一个元素的真正值为a[i]*blockMul[i/size]+blockAdd[i/size]。
  • 每次乘法,对于不完整块的元素,更新其所在的块的所有元素的a[i]值(使其变为真正的值),且清空所在块blockMul和blockAdd的值,然后再乘上c。
  • 每次乘法,对于完整块的元素,这些元素的真正值变为(a[i] * blockMul[i/size] + blockAdd[i/size] )*c,也就是a[i]*blockMul[i/size]*c+blockAdd[i/size]*c。
  • 所以我们只要把这些块的blockMul和blockAdd都乘上c就好了。
  • 每次加法,不完整部分也是更新元素a[i]值,然后清空blockMul和blockAdd,然后加上c;完整块直接加到该块的blockAdd就行了。
  • 每次询问,输出该元素的真正值。
  • 代码:
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#define MOD (10007)
#define LL 
using namespace std;
int size,a[110000],blockMul[110000],blockAdd[110000];
void deal(int x)
{
    int l=x/size*size;
    for(int i=1;i<=size;i++)
        a[l+i-1]=(a[l+i-1]*blockMul[l/size]%MOD+blockAdd[l/size])%MOD;
    blockMul[l/size]=1;
    blockAdd[l/size]=0;
    return ;
}
int main()
{
    for(int i=0;i<=100000;i++)blockMul[i]=1;
    memset(blockAdd,0,sizeof(blockAdd));
    int n;
    scanf("%d",&n);
    size=sqrt(n)+1;
    for(int i=0;i<n;i++)scanf("%d",&a[i]);
    for(int i=1;i<=n;i++)
    {
        int op,l,r,c;
        scanf("%d%d%d%d",&op,&l,&r,&c);
        l--;r--;
        if(op==0)
        {
            int now=l;
            deal(l);deal(r);
            for(;now%size!=0&&now<=r;now++)
                a[now]=(a[now]+c)%MOD;
            for(;now+size-1<=r;now+=size)
                blockAdd[now/size]=(blockAdd[now/size]+c)%MOD;
            for(;now<=r;now++)
                a[now]=(a[now]+c)%MOD;
        }
        if(op==1)
        {
            int now=l;
            deal(l);deal(r);
            for(;now%size!=0&&now<=r;now++)
                a[now]=(a[now]*c)%MOD;
            for(;now+size-1<=r;now+=size)
            {
                blockMul[now/size]=(blockMul[now/size]*c)%MOD;
                blockAdd[now/size]=(blockAdd[now/size]*c)%MOD;
            }
            for(;now<=r;now++)
                a[now]=(a[now]*c)%MOD;
        }
        if(op==2)
            printf("%d\n",(a[r]*blockMul[r/size]%MOD+blockAdd[r/size])%MOD);
    }
    return 0;
}
  • Pro8
  • 又是一题面向数据编程…
  • 出点数据暴力一下,会发现几次询问后基本都是一大段一大段一样的。
  • 使用block[i/size]表示i所在的块是否全部都是一样的,如果是的话,这块的所有元素的值都是a[i/size*size](也就是这个块的第一个元素的值)。
  • 每次查询时对于两端的块如果block=true,那么就需要暴力把两端块内所有元素都换成块内第一个元素(也就是它真正的值)。因为当初我们改的时候只改了块头,其他值都是假的。
  • 每次查询时对于完整的块,如果block=true那么就看看c是否和块头一样,是就加上size;不一样的话就暴力统计。由于基本都是一样的,只有少数几个块block=false,所以实际上还是很快的。
  • 代码:
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#define MOD (10007)
#define LL 
using namespace std;
int a[110000];
bool block[110000];
int main()
{
    memset(block,false,sizeof(block));
    int n,size;
    scanf("%d",&n);
    size=sqrt(n)+1;
    for(int i=0;i<n;i++)scanf("%d",&a[i]);
    for(int i=1;i<=n;i++)
    {
        int l,r,c,ans=0;
        scanf("%d%d%d",&l,&r,&c);
        l--;r--;
        int now=l;
        if(block[now/size])
        {
            now=now/size*size;
            int stnow=now;
            block[now/size]=false;
            for(;now%size!=0||now==stnow;now++)
                a[now]=a[now/size*size];
            now=l;
        }
        for(;now%size!=0&&now<=r;now++)
        {
            if(a[now]==c)
                ans++;
            a[now]=c;
        }
        for(;now+size-1<=r;now+=size)
        {
            if(block[now/size])
            {
                if(a[now]==c)
                    ans+=size;
            }
            else
            {
                for(int i=1;i<=size;i++)
                    if(a[now+i-1]==c)
                        ans++;
            }
            a[now]=c;
            block[now/size]=true;
        }
        if(block[now/size])
        {
            now=now/size*size;int stnow=now;
            block[now/size]=false;
            for(;now%size!=0||now==stnow;now++)
                a[now]=a[now/size*size];
            now=(now-1)/size*size;
        }
        for(;now<=r;now++)
        {
            if(a[now]==c)
                ans++;
            a[now]=c;
        }
        printf("%d\n",ans);
    }
    return 0;
}
  • Pro9
  • 正在研究。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值