数列分块入门(最后一个难啊)

感觉这个不算科技吧,就是现在把操作降在块内了,算是暴力的优化

https://loj.ac/problem/6277

LOJ6277. 数列分块入门 1

对于这些数你有两种操作,0是区间+,1是区间查询

#include<bits/stdc++.h>
using namespace std;
#define N 50005
int m,belong[N];
int a[N],tag[N];
void change(int l,int r,int val)
{
    for(int i=l;i<=min(r,belong[l]*m);i++) a[i]+=val;//将a块内剩余部分暴力更新
    if(belong[l]!=belong[r])
    {
        for(int i=(belong[r]-1)*m+1;i<=r;i++) a[i]+=val;//将b块内剩余部分暴力更新
    }
    for(int i=belong[l]+1;i<=belong[r]-1;i++)tag[i]+=val;//将整块需要加的值更新上去
}
int main()
{
    int n;
    scanf("%d",&n);
    m=sqrt(n);//一块有几个
    for(int i=1;i<=n;i++) belong[i]=(i-1)/m+1;//将每个值分到所给的块内
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    for(int i=1,op,l,r,val;i<=n;i++)
    {
        scanf("%d%d%d%d",&op,&l,&r,&val);
        if(!op)change(l,r,val);
        else printf("%d\n",tag[belong[r]]+a[r]);//tag获取这个序列被加的值
    }
    return 0;
}

LOJ6278. 数列分块入门 2

这个还是区间加,但是要查询区间小于c*c的值

这个就麻烦多了,还要排序的啊

#include<bits/stdc++.h>
using namespace std;
#define N 50005
int n,m,belong[N];
int a[N],tag[N];
vector<int>vec[N];
void reset(int x)
{
    //将两个特殊的块更新
    vec[x].clear();
    for(int i=(x-1)*m+1; i<=min(x*m,n); i++)vec[x].push_back(a[i]);
    sort(vec[x].begin(),vec[x].end());
}
void change(int l,int r,int val)
{
    for(int i=l; i<=min(r,belong[l]*m); i++) a[i]+=val; //将a块内剩余部分暴力更新
    reset(belong[l]);
    if(belong[l]!=belong[r])
    {
        for(int i=(belong[r]-1)*m+1; i<=r; i++) a[i]+=val; //将b块内剩余部分暴力更新
        reset(belong[r]);
    }
    for(int i=belong[l]+1; i<=belong[r]-1; i++)tag[i]+=val; //将整块需要加的值更新上去
}
int query(int l,int r,int val)
{
    int ans=0;
    for(int i=l; i<=min(belong[l]*m,r); i++)if(a[i]+tag[belong[l]]<val)ans++;
    if(belong[l]!=belong[r])
        for(int i=(belong[r]-1)*m+1; i<=r; i++)if(a[i]+tag[belong[r]]<val)ans++;
    for(int i=belong[l]+1; i<=belong[r]-1; i++)
        ans+=lower_bound(vec[i].begin(),vec[i].end(),val-tag[i])-vec[i].begin();
    return ans;
}
int main()
{
    scanf("%d",&n);
    m=sqrt(n);//一块有几个
    for(int i=1; i<=n; i++)belong[i]=(i-1)/m+1; //将每个值分到所给的块内
    for(int i=1; i<=n; i++)scanf("%d",&a[i]),vec[belong[i]].push_back(a[i]);
    for(int i=1;i<=belong[n];i++)sort(vec[i].begin(),vec[i].end());//每块要先保证有序,因为以后不再更新了
    for(int i=1,op,l,r,val; i<=n; i++)
    {
        scanf("%d%d%d%d",&op,&l,&r,&val);
        if(!op)change(l,r,val);
        else printf("%d\n",query(l,r,val*val));
    }
    return 0;
}

 

LOJ6279. 数列分块入门 3

询问区间内小于某个值 x的前驱(比其小的最大元素)

这个上面改改就行了

#include<bits/stdc++.h>
using namespace std;
#define N 100005
int n,m,belong[N];
int a[N],tag[N];
vector<int>vec[N];
void reset(int x)
{
    //将两个特殊的块更新
    vec[x].clear();
    for(int i=(x-1)*m+1; i<=min(x*m,n); i++)vec[x].push_back(a[i]);
    sort(vec[x].begin(),vec[x].end());
}
void change(int l,int r,int val)
{
    for(int i=l; i<=min(r,belong[l]*m); i++) a[i]+=val; //将a块内剩余部分暴力更新
    reset(belong[l]);
    if(belong[l]!=belong[r])
    {
        for(int i=(belong[r]-1)*m+1; i<=r; i++) a[i]+=val; //将b块内剩余部分暴力更新
        reset(belong[r]);
    }
    for(int i=belong[l]+1; i<=belong[r]-1; i++)tag[i]+=val; //将整块需要加的值更新上去
}
int query(int l,int r,int val)
{
    int ans=-1;
    for(int i=l; i<=min(belong[l]*m,r); i++)if(a[i]+tag[belong[l]]<val)ans=max(ans,a[i]+tag[belong[l]]);
    if(belong[l]!=belong[r])
        for(int i=(belong[r]-1)*m+1; i<=r; i++)if(a[i]+tag[belong[r]]<val)ans=max(ans,a[i]+tag[belong[r]]);
    for(int i=belong[l]+1,pos; i<=belong[r]-1; i++)
    {
        pos=lower_bound(vec[i].begin(),vec[i].end(),val-tag[i])-vec[i].begin();
        if(pos>=1) ans=max(ans,vec[i][pos-1]+tag[i]);
    }
    return ans;
}

int main()
{
    scanf("%d",&n);
    m=sqrt(n);//一块有几个
    for(int i=1; i<=n; i++)belong[i]=(i-1)/m+1; //将每个值分到所给的块内
    for(int i=1; i<=n; i++)scanf("%d",&a[i]),vec[belong[i]].push_back(a[i]);
    for(int i=1;i<=belong[n];i++)sort(vec[i].begin(),vec[i].end());//每块要先保证有序,因为以后不更新了
    for(int i=1,op,l,r,val; i<=n; i++)
    {
        scanf("%d%d%d%d",&op,&l,&r,&val);
        if(!op)change(l,r,val);
        else printf("%d\n",query(l,r,val));
    }
    return 0;
}

 当然也可以用set去维护这个块,那个lowwerbound不要用错

#include<stdio.h>
#include<bits/stdc++.h>
using namespace std;
#define N 100005
int n,m,belong[N];
int a[N],tag[N];
set<int>S[N];
void change(int l,int r,int val)
{
    for(int i=l; i<=min(r,belong[l]*m); i++)
    {
        S[belong[l]].erase(a[i]);
        a[i]+=val; //将a块内剩余部分暴力更新
        S[belong[l]].insert(a[i]);
    }
    if(belong[l]!=belong[r])
    {
        for(int i=(belong[r]-1)*m+1; i<=r; i++)
        {
            S[belong[r]].erase(a[i]);
            a[i]+=val; //将b块内剩余部分暴力更新
            S[belong[r]].insert(a[i]);
        }
    }
    for(int i=belong[l]+1; i<=belong[r]-1; i++)tag[i]+=val; //将整块需要加的值更新上去
}
int query(int l,int r,int val)
{
    int ans=-1;
    for(int i=l; i<=min(belong[l]*m,r); i++)if(a[i]+tag[belong[l]]<val)ans=max(ans,a[i]+tag[belong[l]]);
    if(belong[l]!=belong[r])
        for(int i=(belong[r]-1)*m+1; i<=r; i++)if(a[i]+tag[belong[r]]<val)ans=max(ans,a[i]+tag[belong[r]]);
    for(int i=belong[l]+1; i<=belong[r]-1; i++)
    {
        set<int>::iterator it=S[i].lower_bound(val-tag[i]);//小于等于
        if(it!=S[i].begin())ans=max(ans,*(--it)+tag[i]);
    }
    return ans;
}
int main()
{
    scanf("%d",&n);
    m=sqrt(n+0.5);//一块有几个
    for(int i=1; i<=n; i++)belong[i]=(i-1)/m+1; //将每个值分到所给的块内
    for(int i=1; i<=n; i++)scanf("%d",&a[i]),S[belong[i]].insert(a[i]);
    for(int i=1,op,l,r,val; i<=n; i++)
    {
        scanf("%d%d%d%d",&op,&l,&r,&val);
        if(!op)change(l,r,val);
        else printf("%d\n",query(l,r,val));
    }
    return 0;
}

LOJ#6280. 数列分块入门 4

涉及区间求和,第一个代码xjb改改就好

#include<bits/stdc++.h>
using namespace std;
#define N 50005
#define ll long long
int m,belong[N];
ll a[N],sum[N],tag[N];
void change(int l,int r,int val)
{
    for(int i=l;i<=min(r,belong[l]*m);i++) a[i]+=val,sum[belong[l]]+=val;//将a块内剩余部分暴力更新
    if(belong[l]!=belong[r])
    {
        for(int i=(belong[r]-1)*m+1;i<=r;i++) a[i]+=val,sum[belong[r]]+=val;;//将b块内剩余部分暴力更新
    }
    for(int i=belong[l]+1;i<=belong[r]-1;i++)tag[i]+=val;//将整块需要加的值更新上去
}
ll query(int l,int r)//和上面思路差不多
{
    ll ans=0;
    for(int i=l;i<=min(r,belong[l]*m);i++)ans+=a[i]+tag[belong[l]];
    if(belong[l]!=belong[r])
        for(int i=(belong[r]-1)*m+1;i<=r;i++)ans+=a[i]+tag[belong[r]];
    for(int i=belong[l]+1;i<=belong[r]-1;i++) ans+=sum[i]+m*tag[i];
    return ans;
}
int main()
{
    int n;
    scanf("%d",&n);
    m=sqrt(n+0.5);//一块有几个
    for(int i=1;i<=n;i++) belong[i]=(i-1)/m+1;//将每个值分到所给的块内
    for(int i=1;i<=n;i++) scanf("%lld",&a[i]),sum[belong[i]]+=a[i];
    for(int i=1,op,l,r,val;i<=n;i++)
    {
        scanf("%d%d%d%d",&op,&l,&r,&val);
        if(!op)change(l,r,val);
        else printf("%d\n",query(l,r)%(val+1));
    }
    return 0;
}

LOJ#6281. 数列分块入门 5

区间开方,区间求和,怎么那么像呢,这个不是暴力线段树更新?

当然分块也能做啊,上面的题目也可以线段树刷的

#include<bits/stdc++.h>
using namespace std;
#define N 50005
#define ll long long
int m,belong[N];
int a[N],sum[N],tag[N];
void la(int x)
{
    if(tag[x])return;
    tag[x]=1,sum[x]=0;
    for(int i=(x-1)*m+1; i<=x*m; i++)
    {
        a[i]=sqrt(a[i]),sum[x]+=a[i];
        if(a[i]>1) tag[x]=0;
    }
}
void change(int l,int r)
{
    for(int i=l; i<=min(r,belong[l]*m); i++) sum[belong[l]]-=a[i],a[i]=sqrt(a[i]),sum[belong[l]]+=a[i]; //将a块内剩余部分暴力更新
    if(belong[l]!=belong[r])
    {
        for(int i=(belong[r]-1)*m+1; i<=r; i++) sum[belong[r]]-=a[i],a[i]=sqrt(a[i]),sum[belong[r]]+=a[i]; //将b块内剩余部分暴力更新
    }
    for(int i=belong[l]+1; i<=belong[r]-1; i++)la(i);//将整块需要加的值更新上去
}
int query(int l,int r)//和上面思路差不多
{
    int ans=0;
    for(int i=l; i<=min(r,belong[l]*m); i++)ans+=a[i];
    if(belong[l]!=belong[r])
        for(int i=(belong[r]-1)*m+1; i<=r; i++)ans+=a[i];
    for(int i=belong[l]+1; i<=belong[r]-1; i++)ans+=sum[i];
    return ans;
}
int main()
{
    int n;
    scanf("%d",&n);
    m=sqrt(n+0.5);//一块有几个
    for(int i=1; i<=n; i++) belong[i]=(i-1)/m+1; //将每个值分到所给的块内
    for(int i=1; i<=n; i++) scanf("%d",&a[i]),sum[belong[i]]+=a[i];
    for(int i=1,op,l,r,val; i<=n; i++)
    {
        scanf("%d%d%d%d",&op,&l,&r,&val);
        if(!op)change(l,r);
        else printf("%d\n",query(l,r));
    }
    return 0;
}

LOJ#6282. 数列分块入门 6

给出一个长为n的数列,以及n个操作,操作涉及单点插入,单点询问,数据随机生成。

vector的insert或者链表都可以吧,但是数据随机生成啊,就可以分块了

 

#include<bits/stdc++.h>
using namespace std;
#define N 200005
#define ll long long
int m,m1;
int a[N],tag[N];
vector<int>vec[N];
pair<int,int>query(int x)
{
    int now=1;
    while(x>vec[now].size())x-=vec[now].size(),now++;
    return make_pair(now,x-1);
}
void rebuild()
{
    int tot=0;
    for(int i=1; i<=m1; i++)
    {
        for(auto it:vec[i])tag[++tot]=it;
        vec[i].clear();
    }
    int mt=sqrt(tot);
    for(int i=1; i<=tot; i++)
    {
        vec[(i-1)/mt+1].push_back(tag[i]);
    }
    m1=(tot-1)/mt+1;
}

void insert(int pos,int x)
{
    pair<int,int>tmp=query(pos);
    vec[tmp.first].insert(vec[tmp.first].begin()+tmp.second,x);
    if(vec[tmp.first].size()>20*m)rebuild();  //重构
}
int main()
{
    int n;
    scanf("%d",&n);
    m=sqrt(n+0.5);//一块有几个
    for(int i=1; i<=n; i++) scanf("%d",&a[i]),vec[(i-1)/m+1].push_back(a[i]);
    m1=(n-1)/m+1;
    for(int i=1,op,l,r,val; i<=n; i++)
    {
        scanf("%d%d%d%d",&op,&l,&r,&val);
        if(!op)insert(l,r);
        else
        {
            pair<int,int>temp=query(r);
            printf("%d\n",vec[temp.first][temp.second]);
        }
    }
    return 0;
}

LOJ#6283. 数列分块入门 7

有一个区间乘法,还有区间加法,当然这个也可以去暴力更新

更新的时候要注意下,就是我乘起来的更新了,你乘的时候加法的也需要啊

#include<bits/stdc++.h>
using namespace std;
#define MD 10007
#define N 100005
#define ll long long
int m,n,belong[N];
int a[N],atag[N],mtag[N];
void reset(int x)
{
    for(int i=(x-1)*m+1; i<=min(n,x*m); i++)
        a[i]=(a[i]*mtag[x]+atag[x])%MD;
    atag[x]=0,mtag[x]=1;
}
void add(int l,int r,int val)
{
    reset(belong[l]);
    for(int i=l; i<=min(r,belong[l]*m); i++)a[i]=(a[i]+val)%MD;
    if(belong[l]!=belong[r])
    {
        reset(belong[r]);
        for(int i=(belong[r]-1)*m+1; i<=r; i++)a[i]=(a[i]+val)%MD;
    }
    for(int i=belong[l]+1; i<=belong[r]-1; i++)atag[i]=(atag[i]+val)%MD;
}
void mul(int l,int r,int val)
{
    reset(belong[l]);
    for(int i=l; i<=min(r,belong[l]*m); i++)a[i]=(a[i]*val)%MD;
    if(belong[l]!=belong[r])
    {
        reset(belong[r]);
        for(int i=(belong[r]-1)*m+1; i<=r; i++)a[i]=(a[i]*val)%MD;
    }
    for(int i=belong[l]+1; i<=belong[r]-1; i++)atag[i]=(atag[i]*val)%MD,mtag[i]=(mtag[i]*val)%MD;
}
int main()
{
    scanf("%d",&n);
    m=sqrt(n+0.5);//一块有几个
    for(int i=1; i<=n; i++) scanf("%d",&a[i]),belong[i]=(i-1)/m+1;
    for(int i=1; i<=belong[n]; i++)mtag[i]=1;
    for(int i=1,op,l,r,val; i<=n; i++)
    {
        scanf("%d%d%d%d",&op,&l,&r,&val);
        if(op==0)add(l,r,val);
        else if(op==1)mul(l,r,val);
        else  printf("%d\n",(a[r]*mtag[belong[r]]+atag[belong[r]])%MD);
    }
    return 0;
}

LOJ#6284. 数列分块入门 8

查询位于 [l,r]的数字有多少个是 c,再把位于 [l,r]的数字都改为 c

这个题目类似于第五个题目

每次都是修改区间,所以问题变得简单了起来

我每次修改一块的几个,那就是一块的复杂度,然后我只需要对这个块就行修改就好了

对于不完整块,只能把这个tag先更新一遍,再进行求解

tag==-1表示这个块的值不相同,如果改为c恰好是-1呢,所以这个值最好选一个没出现的值吧

所以我对分块有一点浅薄的认识,就是如果暴力来做复杂度很高,而且没有好的(或者说有你不会写)数据结构去维护,这个时候分块的话均摊的复杂度就不大,可以通过这种trick去搞这个题目

#include<bits/stdc++.h>
using namespace std;
#define MD 10007
#define N 100005
#define ll long long
int m,n,belong[N];
int a[N],tag[N];
void reset(int x)
{
    if(tag[x]==-1)return;
    for(int i=(x-1)*m+1; i<=min(n,x*m); i++)a[i]=tag[x];
    tag[x]=-1;
}
int query(int l,int r,int val)
{
    int ans=0;
    reset(belong[l]);
    for(int i=l; i<=min(r,belong[l]*m); i++)
    {
        if(a[i]!=val)a[i]=val;
        else ans++;
    }
    if(belong[l]!=belong[r])
    {
        reset(belong[r]);
        for(int i=(belong[r]-1)*m+1; i<=r; i++)
        {
            if(a[i]!=val)a[i]=val;
            else ans++;
        }
    }
    for(int i=belong[l]+1; i<=belong[r]-1; i++)
    {
        if(tag[i]!=-1)
        {
            if(tag[i]!=val) tag[i]=val;
            else ans+=m;
        }
        else
        {
            for(int j=(i-1)*m+1; j<=i*m; j++)
            {
                if(a[j]!=val) a[j]=val;
                else ans++;
            }
            tag[i]=val;
        }
    }
    return ans;
}
int main()
{
    scanf("%d",&n);
    m=sqrt(n+0.5);//一块有几个
    memset(tag,-1,sizeof tag);
    for(int i=1; i<=n; i++) scanf("%d",&a[i]),belong[i]=(i-1)/m+1;
    for(int i=1,l,r,val; i<=n; i++)
    {
        scanf("%d%d%d",&l,&r,&val);
        printf("%d\n",query(l,r,val));
    }
    return 0;
}

LOJ#6285. 数列分块入门 9

(「BZOJ2724」[Violet 6] 蒲公英)

给出一个长为n的数列,以及n个操作,操作涉及询问区间的最小众数。

最后一个难爆了,表示根本想不出来

应该是完整的所有块的众数,和不完整块中出现的数。

所以我们可以预处理f(i,j)表示第 i 块到第 j 块的众数(枚举 i 开个桶扫一遍)。

那么只要能快速得出一个数在某个区间内出现次数即可,每次只要比较至多2√n+1个元素的出现次数,这题就解决了。

由于没有修改,只要离散化以后,给每个数 x 开个vector,按顺序存下 x 出现的位置,每次询问 x 时把区间的左右端点放进对应 vector 二分一下即可。

根据均值不等式,可以算出分块大小大概是√(n/logn)

抄黄学长的博客,迷迷糊糊算是看懂了吧,感觉好难

#include<bits/stdc++.h>
using namespace std;
int n,m,tot;
int v[50005],belong[50005];
int f[505][505];
map<int,int>M;
int val[50005],cnt[50005];
vector<int>vec[50005];
void pre(int x)
{
    memset(cnt,0,sizeof cnt);
    int mx=0,ans=0;
    for(int i=(x-1)*m+1,t; i<=n; i++)
    {
        cnt[v[i]]++,t=belong[i];
        if(cnt[v[i]]>mx||(cnt[v[i]]==mx&&val[v[i]]<val[ans]))ans=v[i],mx=cnt[v[i]];
        f[x][t]=ans;
    }
}
int query(int l,int r,int x)
{
    int t=upper_bound(vec[x].begin(),vec[x].end(),r)-lower_bound(vec[x].begin(),vec[x].end(),l);
    return t;
}
int query(int a,int b)
{
    int ans,mx;
    ans=f[belong[a]+1][belong[b]-1];
    mx=query(a,b,ans);
    for(int i=a,t; i<=min(belong[a]*m,b); i++)
    {
        t=query(a,b,v[i]);
        if(t>mx||(t==mx&&val[v[i]]<val[ans]))ans=v[i],mx=t;
    }
    if(belong[a]!=belong[b])
        for(int i=(belong[b]-1)*m+1,t; i<=b; i++)
        {
            t=query(a,b,v[i]);
            if(t>mx||(t==mx&&val[v[i]]<val[ans]))ans=v[i],mx=t;
        }
    return ans;
}
int main()
{
    scanf("%d",&n);
    m=200;
    //直接给定块大小,这个比较优秀,这个要根据均值不等式求得,在分块中,不同的块大小设置很影响程序的快慢
    for(int i=1; i<=n; i++)
    {
        scanf("%d",&v[i]);
        if(!M[v[i]])M[v[i]]=++tot,val[tot]=v[i];
        v[i]=M[v[i]],vec[v[i]].push_back(i);
    }
    for(int i=1; i<=n; i++)belong[i]=(i-1)/m+1;
    for(int i=1; i<=belong[n]; i++)pre(i);
    for(int i=1,l,r; i<=n; i++)
    {
        scanf("%d%d",&l,&r);
        printf("%d\n",val[query(l,r)]);
    }
    return 0;
}

 

posted @ 2018-09-12 09:35  暴力都不会的蒟蒻  阅读(533)  评论(0编辑  收藏  举报