分块记录

分块思想:大段维护,小块暴力 

大佬博客:http://hzwer.com/8053.html

题目链接:https://loj.ac/problems/tag/207

一个比较好的理解视频:B站

L[]表示该块的左区间端点,R[]表示该块的右区间端点,belong[]表示这个下标数字属于哪个块。

一、区间加法,单点查值(咕咕咕)

 

二、区间加值,求区间内小于x的数的个数

大段直接标记一个数组算作整体加,小块内暴力给被修改的数字加x。

我们对每个块内部的数字排序,那么一个块内小于x的数的个数就可以通过二分来求得了。

当一个块没有被完全修改的时候,我们需要将这个块内的数字重新排序。

#include<bits/stdc++.h>
using namespace std;
const int maxn=5e4+9;

typedef long long ll;

ll a[maxn];
ll b[maxn];
int L[maxn],R[maxn],belong[maxn];

ll add[maxn];
int kuai;
int len;

void build(int n){
    kuai=len=sqrt(n);
    for(int i=1;i<=kuai;++i)
        L[i]=(i-1)*len+1,R[i]=i*len;
    if(R[kuai]<n){
        ++kuai;
        L[kuai]=(kuai-1)*len+1;
        R[kuai]=n;
    }

    for(int i=1;i<=kuai;++i)
        sort(b+L[i],b+R[i]+1);
    for(int i=1;i<=kuai;++i)
        for(int j=L[i];j<=R[i];++j)
            belong[j]=i;
}

void updata(int l,int r,int x){
    int p=belong[l],q=belong[r];
    if(p==q){
        for(int i=l;i<=r;++i) a[i]+=x;
        for(int i=L[p];i<=R[p];++i) b[i]=a[i];
        sort(b+L[p],b+R[p]+1);
    }
    else{
        for(int i=p+1;i<=q-1;++i)
            add[i]+=x;
        for(int i=l;i<=R[p];++i) a[i]+=x;
        for(int i=L[q];i<=r;++i) a[i]+=x;
        for(int i=L[p];i<=R[p];++i) b[i]=a[i];
        for(int i=L[q];i<=R[q];++i) b[i]=a[i];
        sort(b+L[p],b+R[p]+1);
        sort(b+L[q],b+R[q]+1);
    }
}

int erfen(int l,int r,ll k){
    int mid;
    while(l<=r){
        mid=(l+r)>>1;
        if(b[mid]<k) l=mid+1;
        else r=mid-1;
    }
    return r;
}

int myfind(int l,int r,ll x){
    int p=belong[l],q=belong[r];
    int res=0;
    if(p==q){
        for(int i=l;i<=r;++i)
            if(add[p]+a[i]<x) ++res;
    }
    else{
        for(int i=p+1;i<=q-1;++i)
            res+=erfen(L[i],R[i],x-add[i])-L[i]+1;
        for(int i=l;i<=R[p];++i)
            if(add[p]+a[i]<x) ++res;
        for(int i=L[q];i<=r;++i)
            if(add[q]+a[i]<x) ++res;
    }
    return res;
}

int main(){
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;++i){
        scanf("%I64d",&a[i]);
        b[i]=a[i];
    }
    int id,l,r,x;
    build(n);
    for(int i=1;i<=n;++i){
        scanf("%d%d%d%d",&id,&l,&r,&x);
        if(id){
            printf("%d\n",myfind(l,r,x*1LL*x));
        }
        else{
            updata(l,r,x);
        }
    }

    return 0;
}

 

三、区间加值,求区间内x的前驱

与上一道题维护的东西还有的修改操作一致。

查询操作的话就是:[L,R]

我们找到p是L所在的块,q是R所在的块,那么如果p,q不是一个块的话,我们对于[P+1,Q-1]范围内的每个块,二分找最后一个小于x的数字是谁,找到这若干个块中的较大值,然后对于两端的小段,我们暴力判断a[i]+add[p]是否可以成为答案即可。

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+7;

typedef long long ll;

ll a[maxn];
ll b[maxn];
int L[maxn],R[maxn],belong[maxn];

ll add[maxn];
int kuai;
int len;

void build(int n){
    kuai=len=sqrt(n);
    for(int i=1;i<=kuai;++i)
        L[i]=(i-1)*len+1,R[i]=i*len;
    if(R[kuai]<n){
        ++kuai;
        L[kuai]=(kuai-1)*len+1;
        R[kuai]=n;
    }

    for(int i=1;i<=kuai;++i)
        sort(b+L[i],b+R[i]+1);
    for(int i=1;i<=kuai;++i)
        for(int j=L[i];j<=R[i];++j)
            belong[j]=i;
}

void updata(int l,int r,int x){
    int p=belong[l],q=belong[r];
    if(p==q){
        for(int i=l;i<=r;++i) a[i]+=x;
        for(int i=L[p];i<=R[p];++i) b[i]=a[i];
        sort(b+L[p],b+R[p]+1);
    }
    else{
        for(int i=p+1;i<=q-1;++i)
            add[i]+=x;
        for(int i=l;i<=R[p];++i) a[i]+=x;
        for(int i=L[q];i<=r;++i) a[i]+=x;
        for(int i=L[p];i<=R[p];++i) b[i]=a[i];
        for(int i=L[q];i<=R[q];++i) b[i]=a[i];
        sort(b+L[p],b+R[p]+1);
        sort(b+L[q],b+R[q]+1);
    }
}

int erfen(int l,int r,ll x){
    int mid;
    while(l<=r){
        mid=(l+r)>>1;
        if(b[mid]<x) l=mid+1;
        else r=mid-1;
    }
    return r;
}

const ll hh=-1e18;
ll myfind(int l,int r,ll x){
    int p=belong[l],q=belong[r];
    ll res=hh;
    if(p==q){
        for(int i=l;i<=r;++i)
            if(a[i]+add[p]<x&&a[i]+add[p]>res)
                res=a[i]+add[p];
    }
    else{
        for(int i=p+1;i<=q-1;++i){
            int rr=erfen(L[i],R[i],x-add[i]);
            if(rr<L[i]) continue;
            if(b[rr]+add[i]>res) res=b[rr]+add[i];
        }
        for(int i=l;i<=R[p];++i)
            if(a[i]+add[p]<x&&a[i]+add[p]>res)
                res=a[i]+add[p];
        for(int i=L[q];i<=r;++i)
            if(a[i]+add[q]<x&&a[i]+add[q]>res)
                res=a[i]+add[q];

    }
    return res==hh?-1:res;
}

int main(){
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;++i){
        scanf("%I64d",&a[i]);
        b[i]=a[i];
    }
    int id,l,r,x;
    build(n);
    for(int i=1;i<=n;++i){
        scanf("%d%d%d%d",&id,&l,&r,&x);
        if(id){
            printf("%d\n",myfind(l,r,x));
        }
        else{
            updata(l,r,x);
        }
    }

    return 0;
}

 

四、区间加值,区间求和

POJ 3468

使用sum数组用来记录当一个块没有全部被修改时,被修改的数字个数乘上要加的值;

add数组记录当一整个块修改时的修改要加上的值;

a数组原数组,顺便加上一个块没有被全部修改时,被修改的数字进行单点加值;

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int inf=0x3f3f3f3f;
const int maxn=1e5+7;
ll a[maxn];
ll sum[maxn];
ll add[maxn];
int L[maxn],R[maxn],pos[maxn];
int n,m,t;

void updata(int l,int r,ll x){
    int p=pos[l],q=pos[r];
    if(p==q){
        for(int i=l;i<=r;++i) a[i]+=x;
        sum[p]+=(r-l+1)*x;
    }
    else{
        for(int i=p+1;i<=q-1;++i) add[i]+=x;
        for(int i=l;i<=R[p];++i) a[i]+=x;
        for(int i=L[q];i<=r;++i) a[i]+=x;
        sum[p]+=1LL*(R[p]-l+1)*x;
        sum[q]+=1LL*(r-L[q]+1)*x;
    }
}

ll myfind(int l,int r){
    ll res=0;
    int p=pos[l],q=pos[r];
    if(p==q){
        for(int i=l;i<=r;++i)
            res+=a[i];
        res+=1LL*(r-l+1)*add[p];
    }
    else{
        for(int i=l;i<=R[p];++i) res+=a[i];
        res+=1LL*(R[p]-l+1)*add[p];
        for(int i=p+1;i<=q-1;++i) res+=sum[i]+add[i]*(R[i]-L[i]+1);
        for(int i=L[q];i<=r;++i) res+=a[i];
        res+=1LL*(r-L[q]+1)*add[q];
    }
    return res;
}

void init(){
    t=sqrt(n);
    for(int i=1;i<=t;++i){
        L[i]=(i-1)*t+1;
        R[i]=i*t;
        //cout<<L[i]<<" "<<R[i]<<endl;
    }
    if(R[t]<n){
        ++t;
        L[t]=R[t-1]+1;
        R[t]=n;
    }
    for(int i=1;i<=t;++i)
        for(int j=L[i];j<=R[i];++j)
            pos[j]=i,sum[i]+=a[j];

}

char s[3];
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i) scanf("%lld",&a[i]);
    init();
    int l,r;
    ll x;
    while(m--){
        scanf("%s%d%d",s,&l,&r);
        if(s[0]=='Q') printf("%lld\n",myfind(l,r));
        else{
            scanf("%lld",&x);
            updata(l,r,x);
        }
    }
    return 0;

}

 

五、区间开方,区间求和

一个int范围内的数字最多开六七次根号就会到1,那么对于整块我们可以设置一个标记来表示这个块内的数字是不是都开方到了1或0,若达到了则跳过,否则对于块内的非0,1数字暴力开方,对于修改非整个块的数字直接暴力就好了。

PS:这个复杂度我是真算不出来。。。。

一个数字最多被开log次一定可以变成1,(0还是0),那么考虑对每个数字自己开方,最坏的情况下,每个数字都开满log次,那么总的复杂度就是n*logn。

原来以为是数据弱了,去bzoj上交了一道3211的题,没想到也能过,只不过比线段树的时间开销大了很多。

下面这个代码是bzoj 3211的,交loj的话改改输入方式

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;

const int maxn=1e5+7;
int a[maxn];
ll sum[maxn];

int f[maxn];//对于长度为n的序列标记该位置的数字需不需要开方;
int check[maxn];//标记这个块内还有没有需要开方的数字;

int L[maxn],R[maxn],belong[maxn],kuai,len;

void build(int n){
    kuai=len=sqrt(n);
    for(int i=1;i<=kuai;++i)
        L[i]=(i-1)*kuai+1,R[i]=i*kuai;
    if(R[kuai]<n){
        ++kuai;
        L[kuai]=(kuai-1)*len+1;
        R[kuai]=n;
    }
    for(int i=1;i<=kuai;++i){
        for(int j=L[i];j<=R[i];++j){
            belong[j]=i;
            if(a[j]==0||a[j]==1) f[j]=1;
            check[i]+=f[j];
            sum[i]+=a[j];
        }
    }
}

void updata(int l,int r){
    int p=belong[l],q=belong[r];
    if(p==q){
        for(int i=l;i<=r;++i)
            if(!f[i]){
                sum[p]-=a[i];
                a[i]=sqrt(a[i]);
                sum[p]+=a[i];
                if(a[i]<=1){
                    f[i]=1;
                    check[p]++;
                }
            }
    }
    else{
        for(int i=p+1;i<=q-1;++i){
            if(check[i]==R[i]-L[i]+1) continue;
            for(int j=L[i];j<=R[i];++j){
                if(check[i]==R[i]-L[i]+1) continue;
                if(!f[j]){
                    sum[i]-=a[j];
                    a[j]=sqrt(a[j]);
                    sum[i]+=a[j];
                    if(a[j]<=1){
                        f[j]=1;
                        check[i]++;
                    }
                }

            }
        }

        for(int i=l;i<=R[p];++i)
            if(!f[i]){
                sum[p]-=a[i];
                a[i]=sqrt(a[i]);
                sum[p]+=a[i];
                if(a[i]<=1){
                    f[i]=1;
                    check[p]++;
                }
            }
        for(int i=L[q];i<=r;++i)
            if(!f[i]){
                sum[q]-=a[i];
                a[i]=sqrt(a[i]);
                sum[q]+=a[i];
                if(a[i]<=1){
                    f[i]=1;
                    check[q]++;
                }
            }
    }

}

ll myfind(int l,int r){
    int p=belong[l],q=belong[r];
    ll res=0;
    if(p==q){
        for(int i=l;i<=r;++i) res+=a[i];
    }
    else{
        for(int i=p+1;i<=q-1;++i) res+=sum[i];
        for(int i=l;i<=R[p];++i) res+=a[i];
        for(int i=L[q];i<=r;++i) res+=a[i];
    }
    return res;
}

int main(){
    int n;
    int id,l,r,x;
    scanf("%d",&n);
    for(int i=1;i<=n;++i)
        scanf("%lld",&a[i]);
    build(n);
    int m;
    scanf("%d",&m);
    for(int i=1;i<=m;++i){
        scanf("%d%d%d",&id,&l,&r);
        if(id==1){
            printf("%lld\n",myfind(l,r));
        }
        else{
            updata(l,r);
        }
    }

    return 0;
}

 

六、静态区间第k大

不带修改的静态第k大,主席树大家应该都会了,这里说一下分块怎么搞。

分块有两种做法吧。

可以先离散化。

第一种:预处理的时候,把原序列复制一份,对块内元素进行排序。选定二分区间为离散化后的元素个数,我们二分最后的答案看比他区间内比他小的数字个数有多少个,对于整块的话由于之前我们复制了一份对块内元素排序的数组,二分找比他小的数字的个数,不完整的块在原数组中比大小就可以,这样最后就一定可以找到答案。

时间复杂度为:m次查询,单次查找为log(数字离散化后的长度)*分的块数量*log(块大小)。

我选择了将块数分为\sqrt{n},复杂度最后为:m*logn*(\sqrt{n}*log\sqrt{n}+\sqrt{n})

 

第二种:空间开销更大了,我们这次对于每个块都开一个权值数组记录每个数出现的个数,为了方便后面求小于一个数字的个数,我们把这个块内的权值数组做成前缀和的形式,那么查询的时候就可以O(1)的查找小于某数的个数了,就可以省下一个log\sqrt{n}了,于是复杂度变为了m*logn*(\sqrt{n}+\sqrt{n})

当然我们还有第三种做法,我们与其维护了块内的权值,不如直接维护一个块的前缀和,即对于第i个块来说,我们维护的是从1~i块内的数字的权值前缀和,那么每次查找大段的时候就是O(1)的操作,于是复杂度变为了:m*logn*\sqrt{n},引用文章开头b站up主的话,这种做法相当于半可持久化

第二种的话需要开一个int sum[sqrt(n)][n]这么大一个数组,我还暂时没找到能开这么大的题,就只拿第一种方法做了区间第k大的经典题目poj 2104

第一条记录是主席树,第二条是第一种做法:

#include<cstdio>
#include<algorithm>
#include<cmath>
#include<iostream>
using namespace std;

typedef long long ll;

const int maxn=1e5+7;

int m;
int a[maxn],b[maxn],c[maxn];

void quchong(int n){
    sort(b+1,b+1+n);
    m=unique(b+1,b+1+n)-b-1;
}

int getid(int x){
    return lower_bound(b+1,b+1+m,x)-b;
}

int kuai,len;
int L[319],R[319],belong[maxn];


void build(int n){
    len=kuai=sqrt(n);
    for(int i=1;i<=kuai;++i)
        L[i]=(i-1)*kuai+1,R[i]=i*kuai;
    if(R[kuai]<n){
        ++kuai;
        L[kuai]=R[kuai-1]+1;
        R[kuai]=n;
    }
    for(int i=1;i<=kuai;++i){
        for(int j=L[i];j<=R[i];++j){
            belong[j]=i;
            a[j]=getid(a[j]);
            c[j]=a[j];
        }

    }
    for(int i=1;i<=kuai;++i)
        sort(c+L[i],c+R[i]+1);
}

//找最后一个小于x的下标;
int erfen2(int l,int r,int x){
    int mid;
    while(l<=r){
        mid=(l+r)>>1;
        if(c[mid]<x) l=mid+1;
        else r=mid-1;
    }
    return r;
}
//在区间l,r内查找小于x的个数;
int myfind(int l,int r,int x){
    int p=belong[l],q=belong[r];
    int res=0;
    if(p==q){
        for(int i=l;i<=r;++i)
            if(a[i]<x) ++res;
    }
    else{
        for(int i=p+1;i<=q-1;++i)
            res+=erfen2(L[i],R[i],x)-L[i]+1;
        for(int i=l;i<=R[p];++i)
            if(a[i]<x) ++res;
        for(int i=L[q];i<=r;++i)
            if(a[i]<x) ++res;
    }
    return res;
}

//二分满足k的答案;
int erfen(int ll,int rr,int k){
    int l=1,r=m,mid;
    while(l<=r){
        mid=(l+r)>>1;
        if(myfind(ll,rr,mid)<k) l=mid+1;
        else r=mid-1;
    }
    return l-1;
}

int main(){
    int n,q;
    scanf("%d%d",&n,&q);
    for(int i=1;i<=n;++i){
        scanf("%d",&a[i]);
        b[i]=a[i];
    }
    quchong(n);
    build(n);
    int l,r,k;
    while(q--){
        scanf("%d%d%d",&l,&r,&k);
        printf("%d\n",b[erfen(l,r,k)]);
    }


}

 

七、区间改值,区间询问等于x的个数

HDU-4391

先说第一种做法,还是对每个块的块内数字进行排序,查询的时候块内的答案就是upper_bound()-lower_bound(),设置一个标记用来标记这个块内的元素是不是被修改成了相同元素,每次修改大段采用这种方式,小块内不完整的修改直接暴力做,完后重新排序该块。我们的标记该如何用掉呢,就是当我们需要修改或者询问一个不完整的块的时候,这种时候我们需要暴力去修改或查询,这时候就需要把标记用掉了。排序这种做法不再赘述了。

第二种做法:

对于每个块设置一个标记数组来表示这个区间是不是被修改成了相同的数字,同时每个块开一个map数组记录数字出现的次数。

当我们修改一个区间时,对于整个块i来说,首先清空他的map,然后把该块的标记置为x,然后map[i][x]=块大小。

修改不完整的块时,首先判断这个块有没有标记v,如果有的话我们需要先把原数组都赋值为v,假设此时要修改的元素为x,那么我们可以暴力的去一个数字一个数字的修改,遍历一次这个不完整的块,如下:

//lazy[]为标记,m[][]为map,a[]为原数组
void pushdown(int p){

    for(int i=L[p];i<=R[p];++i) a[i]=lazy[p];
    lazy[p]=-1;
}

if(lazy[p]!=-1)
    pushdown(p);
//修改不完整的块p中的[l,r]小区间;
for(int i=l;i<=r;++i)
    --m[p][a[i]],++m[p][x],a[i]=x;

如果把块的大小置为\sqrt{n}的话,总的复杂度为:m*(\sqrt{n}*log2(n)+\sqrt{n}*log2(n))

这道题需要注意的地方就是我们要理清楚什么时候需要使用掉标记。

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;

const int maxn=1e5+7;

int a[maxn];
map<int,int> m[319];

int kuai;
int L[319],R[319],belong[maxn];
int lazy[319];

void build(int n){
    for(int i=1;i<319;++i) m[i].clear(),lazy[i]=-1;
    kuai=sqrt(n);
    for(int i=1;i<=kuai;++i)
        L[i]=(i-1)*kuai+1,R[i]=i*kuai;
    if(R[kuai]<n){
        ++kuai;
        L[kuai]=R[kuai-1]+1;
        R[kuai]=n;
    }
    for(int i=1;i<=kuai;++i)
        for(int j=L[i];j<=R[i];++j){
            belong[j]=i;
            m[i][a[j]]++;
        }
}

void pushdown(int p){

    for(int i=L[p];i<=R[p];++i) a[i]=lazy[p];
    lazy[p]=-1;
}

void updata(int l,int r,int x){
    int p=belong[l],q=belong[r];
    if(p==q){
        if(lazy[p]!=-1)
            pushdown(p);
        for(int i=l;i<=r;++i)
            --m[p][a[i]],++m[p][x],a[i]=x;
    }
    else{
        for(int i=p+1;i<=q-1;++i){
            m[i].clear();
            m[i][x]=R[i]-L[i]+1;
            lazy[i]=x;
        }
        if(lazy[p]!=-1) pushdown(p);
        if(lazy[q]!=-1) pushdown(q);
        for(int i=l;i<=R[p];++i)
            --m[p][a[i]],++m[p][x],a[i]=x;
        for(int i=L[q];i<=r;++i)
            --m[q][a[i]],++m[q][x],a[i]=x;
    }

}

int myfind(int l,int r,int x){
    int p=belong[l],q=belong[r];
    int res=0;
    if(p==q){
        if(lazy[p]!=-1) pushdown(p);
        for(int i=l;i<=r;++i)
            if(a[i]==x) ++res;
    }
    else{
        for(int i=p+1;i<=q-1;++i){
            if(lazy[i]!=-1){
                if(lazy[i]==x) res+=R[i]-L[i]+1;
                continue;
            }
            if(m[i].count(x)) res+=m[i][x];
        }
        if(lazy[p]!=-1) pushdown(p);
        if(lazy[q]!=-1) pushdown(q);
        for(int i=l;i<=R[p];++i)
            if(a[i]==x) ++res;
        for(int i=L[q];i<=r;++i)
            if(a[i]==x) ++res;
    }
    return res;
}

int main(){
    //cout<<(int)(maxn*(sqrt(maxn)+sqrt(maxn)*log2(maxn)))<<endl;
    int n;
    int q;
    while(scanf("%d%d",&n,&q)!=EOF){
        for(int i=1;i<=n;++i) scanf("%d",&a[i]);
        build(n);
        int id,l,r,x;
        while(q--){
            scanf("%d%d%d%d",&id,&l,&r,&x);
            ++l,++r;
            if(id==1){
                updata(l,r,x);
            }
            else{
                printf("%d\n",myfind(l,r,x));
            }
        }
    }

    return 0;
}

 

八、区间乘法,区间加法,单点查询

对于这道题目来说我们需要理解清楚乘法与加法对答案的不同影响。

我们设两个标记数组,一个mutil[]表示对一个完整的块做乘法,一个add[]表示对一个完整的块做加法。

先说对于一整个块怎么修改,如果只是加x的话,直接给add数组加上x;乘法的话就需要给add数组以及mutil数组都乘上x。

因为最后我们求一个数字x时,是遵循一个公式:a[x]*mutil[ belong[x] ]+add[ belong[x] ]。

那么不完整的块该如何修改呢。

考虑这样一件事情,假设要修改的区间是不完整的块区间为[l,r],这个区间属于块p,对于块p来说,之前有一次完整的

加法,如果这时候要给[l,r]乘一个数字x,我们是不是需要先把块p的加法标记先都加到对应的a数组里,并且把标记清除,然后才能给[l,r]区间内的数字都乘上x呢,这里一定要好好想一下。因为这个add数组的影响是对于整个块的,而我们加x只是对块p的一小部分做修改。那么对于加法来说也是同理。

所以当我们需要修改一个不完整块的时候,需要加法标记,乘法标记赋给a数组,并将标记消除,才能把新的修改赋值给a数组

时间复杂度:n*(\sqrt{n}+\sqrt{n})

由于这道题目给的时限很小,所以我换了一种写法,因为上面那几种写法的常数还是很大,if语句用的多了真的会有很大的影响,从TLE到AC。

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;

const int maxn=1e5+7;

ll a[maxn];
ll add[maxn],mutil[maxn];

int kuai;
int L[319],R[319],belong[maxn];
const int mod=10007;

void build(int n){
    kuai=sqrt(n);
    for(int i=1;i<=kuai;++i)
        L[i]=(i-1)*kuai+1,R[i]=i*kuai;
    if(R[kuai]<n){
        ++kuai;
        L[kuai]=R[kuai-1]+1;
        R[kuai]=n;
    }
    for(int i=1;i<=kuai;++i){
        add[i]=0,mutil[i]=1;
        for(int j=L[i];j<=R[i];++j)
            belong[j]=i;
    }
}

inline void updata(int l,int r,int id,int x){
    int p=belong[l],q=belong[r];
    if(id==0){//加法;
        for(int i=L[p];i<=R[p];++i)
            a[i]=(a[i]*mutil[p]+add[p])%mod;
        mutil[p]=1,add[p]=0;
        for(int i=l;i<=min(R[p],r);++i)
            a[i]=(a[i]+x)%mod;
        if(p!=q){
            for(int i=p+1;i<=q-1;++i)
                add[i]=(add[i]+x)%mod;
            for(int i=L[q];i<=R[q];++i)
                a[i]=(a[i]*mutil[q]+add[q])%mod;
            mutil[q]=1,add[q]=0;
            for(int i=L[q];i<=r;++i)
                a[i]=(a[i]+x)%mod;
        }
    }
    else{
        for(int i=L[p];i<=R[p];++i)
            a[i]=(a[i]*mutil[p]+add[p])%mod;
        mutil[p]=1,add[p]=0;
        for(int i=l;i<=min(R[p],r);++i)
            a[i]=(a[i]*x)%mod;
        if(p!=q){
            for(int i=p+1;i<=q-1;++i)
                add[i]=(add[i]*x)%mod,mutil[i]=(mutil[i]*x)%mod;
            for(int i=L[q];i<=R[q];++i)
                a[i]=(a[i]*mutil[q]+add[q])%mod;
            mutil[q]=1,add[q]=0;
            for(int i=L[q];i<=r;++i)
                a[i]=(a[i]*x)%mod;
        }
    }
}

inline ll myfind(int x){
    int p=belong[x];
    return (a[x]*mutil[p]+add[p])%mod;
}

int main(){
    int n;
    int id,l,r,x;
    scanf("%d",&n);
    for(register int i=1;i<=n;++i){
        scanf("%lld",&a[i]);
    }
    build(n);
    for(int i=1;i<=n;++i){
        scanf("%d%d%d%d",&id,&l,&r,&x);
        if(id==0){
            updata(l,r,0,x);
        }
        else if(id==1){
            updata(l,r,1,x);
        }
        else{
            printf("%lld\n",myfind(r));
        }
//        cout<<endl;
//        for(int j=1;j<=n;++j){
//            cout<<a[j]*mutil[belong[j]]+add[belong[j]]<<" ";
//        }
//        cout<<endl;
    }
    return 0;
}

 

九、跳跃加值,区间查询

ps:这道题我没有找到可以交的地方,如果有错误的地方欢迎指出,谢谢~

给序列,长度为1e5,操作数也为1e5,支持两种操作

1.ADD D X 从位置1开始,每隔d个位置,给这个位置上的数字加上x;

2.QUERY L R 查询区间[l,r]区间的和。

题目来自2013年国家集训队《浅谈分块思想在一类数据处理问题中的应用》--罗剑桥

设序列长度为n

我们分情况讨论修改操作,如果d>=\sqrt{n},显然从1开始往后跳不会超过\sqrt{n}步,那么我们可以直接暴力修改a数组的值;

如果d<\sqrt{n},我们开一个数组在d位置处加x,只花费O(1)的时间。

那么查询区间[l,r]内的数字和的话,由于d<\sqrt{n}的d取值不会超过\sqrt{n},所以我们可以直接枚举d的取值计算他对[l,r]区间的值的贡献。具体贡献多少就是解两个小方程了:1+x*d>=l,1+y*d<=r,解出左右端点即可。

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;

const int maxn=1e5+7;

ll a[maxn];
ll D[319];
ll sum[319];

int kuai;
int L[319],R[319],belong[maxn];
const int mod=10007;

void build(int n){
    kuai=sqrt(n);
    for(int i=1;i<=kuai;++i)
        L[i]=(i-1)*kuai+1,R[i]=i*kuai;
    if(R[kuai]<n){
        ++kuai;
        L[kuai]=R[kuai-1]+1;
        R[kuai]=n;
    }
    for(int i=1;i<=kuai;++i){
        D[i]=0;
        for(int j=L[i];j<=R[i];++j){
            belong[j]=i;
            sum[i]+=a[j];
        }
    }
}

void updata(int d,int x,int n){
    if(d>=kuai){
        for(int i=1;i<=n;i+=d){
            a[i]+=x;
            sum[belong[i]]+=x;
        }
    }
    else{
        D[d]+=x;
    }
}

ll myfind(int l,int r){
    ll res=0;
    int p=belong[l],q=belong[r];
    for(int i=l;i<=min(R[p],r);++i) res+=a[i];
    if(p!=q){
        for(int i=p+1;i<=q-1;++i) res+=sum[i];
        for(int i=L[q];i<=r;++i) res+=a[i];
    }
    //枚举d;
    for(int i=0;i<kuai;++i)
        if(i)
            res+=((r-1)/i-ceil((l*1.0-1.0)/i)+1)*D[i];
        else if(l==1) res+=D[i];
    return res;
}

char s[9];
int main(){
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;++i)
        scanf("%lld",&a[i]);
    build(n);
    int q;
    scanf("%d",&q);
    int d,l,r,x;
    while(q--){
        scanf("%s",s);
        if(s[0]=='A'){
            scanf("%d%d",&d,&x);
            updata(d,x,n);
        }
        else{
            scanf("%d%d",&l,&r);
            printf("%lld\n",myfind(l,r));
        }

    }

    return 0;
}
/*
10
1 2 3 4 5 6 7 8 9 10
9
Q 4 10
A 1 5
Q 1 10
A 2 3
Q 1 10
Q 5 10
A 3 10
Q 1 10
Q 2 5

*/

/*
49
105
120
84
160
50

*/

 

十、bzoj 2002 弹飞绵羊

给具有N个正整数的序列a[],M次操作,每次可以:

1.修改一个数字;

2.查询从x位置开始,每次将x变为x+a[x],问x>n需要进行多少次变化。

我们处理出两个信息b[],c[],一个是从i位置跳出i所在的块需要的步数,一个是从i位置跳出该块所到的位置。

对于修改来说,设块为p,位置为i,O(1)的修改掉原数组a,然后,我们需要将[ L[p] , i ]内的b[],c[]数组修改掉。

为什么呢,看图

为了保证灰色箭头的跳跃之后的继续跳跃的正确性,我们不能只修改i位置的b[],c[]数组。

因为在区间[ L[p] , i ]范围的数字他可能会经过被修改的i位置跳出去,所以我们也需要修改掉这个范围内的b[],c[]数组来保证答案的正确性。

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;

const int maxn=2e5+7;

ll a[maxn];//原数组;
int b[maxn];//从当前位置跳出该块所需的步数;
ll c[maxn];//从当前位置跳出该块跳到的位置;

int kuai;
int L[500],R[500],belong[maxn];

void build(int n){
    kuai=sqrt(n);
    for(int i=1;i<=kuai;++i)
        L[i]=(i-1)*kuai+1,R[i]=i*kuai;
    if(R[kuai]<n){
        ++kuai;
        L[kuai]=R[kuai-1]+1;
        R[kuai]=n;
    }
    for(int i=1;i<=kuai;++i)
        for(int j=L[i];j<=R[i];++j)
            belong[j]=i;

    for(int i=1;i<=n;++i){
        int z=i;
        b[i]=0;
        while(z<=n&&belong[z]==belong[i]){
            ++b[i];
            z+=a[z];
        }
        c[i]=z;
    }
}

void updata(int id,int x,int n){
    if(id>n) return ;
    int p=belong[id];
    a[id]=x;
    for(int i=id;i>=L[p];--i)
        if(i+a[i]>n||belong[i+a[i]]!=p) b[i]=1,c[i]=i+a[i];
        else b[i]=b[i+a[i]]+1,c[i]=c[i+a[i]];
}

int myfind(int x,int n){
    if(x>n) return 0;
    int res=0;
    while(x<=n){
        res+=b[x];
        x=c[x];
    }
    return res;
}

int main(){
    int n,q;
    scanf("%d",&n);
    for(int i=1;i<=n;++i) scanf("%lld",&a[i]);
    build(n);
    scanf("%d",&q);
    int id,j,x;
    while(q--){
        scanf("%d%d",&id,&j);
        ++j;
        if(id==1){
            printf("%d\n",myfind(j,n));
        }
        else{
            scanf("%d",&x);
            updata(j,x,n);
        }
    }
    return 0;

}

 

十一、bzoj 2724 蒲公英

AcWing上可以交这道题:蒲公英

题意:强制在线静态求区间众数

数据范围:n=4e4,m=5e4,a为正整数。

我们预处理出数组f[][],表示i到j块内的众数是谁,ci[][]表示i到j块内众数出现的次数。

然后对于每个数字开一个vector存出现的位置。

查询时,众数要么是完整的块内出现的,要么是两端不完整的块中出现的数字,那么对于两端不完整的数字我们对其挨个进行二分,找到在[l,r]区间内出现的次数,看是否为答案。

我们设序列长度为n询问次数为m分成了p块,那么一块内数字个数就为n/p,那么预处理f[][]数组所需要的时间为:n*p;查询的时候为m*n/p*log(n/p)。

我们考虑将折预处理的时间复杂度与查询的复杂度折中一下:

即:n*p=m*n/p*log(n/p)),解得p=m*log(n)。

但是我p开成这样T在了最后一个点上,于是改成了n*log(n)。。。。。就过了

当然万事先离散化。

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;

const int maxn=4e4+7;

int a[maxn];
int b[maxn];
int res[900][900];//[i,j]块之间的众数;
int ci[900][900];//[i,j]块的众数其出现次数;

int m;
void quchong(int n){
    sort(b+1,b+1+n);
    m=unique(b+1,b+1+n)-b-1;
}

int getid(int x){
    return lower_bound(b+1,b+1+m,x)-b;
}

int kuai;//块数;
int len;//块长;

int L[900],R[900];
int belong[maxn];
vector<int> v[maxn];

void build(int n,int q){
    kuai=sqrt(n*log2(n));//当n为1时块为0;
    len=kuai?n/kuai:n;

    for(int i=1;i<=kuai;++i)
        L[i]=(i-1)*len+1,R[i]=i*len;
    if(R[kuai]<n){
        ++kuai;
        L[kuai]=R[kuai-1]+1;
        R[kuai]=n;
    }

    for(int i=1;i<=kuai;++i)
        for(int j=L[i];j<=R[i];++j){
            a[j]=getid(a[j]);
            v[a[j]].push_back(j);
            belong[j]=i;
        }
}

int sum[maxn];
//处理从p块开始到他后面的块的众数;
void chuli(int p,int n){
    memset(sum,0,sizeof(sum));
    int c=0,ans=0;
    for(int i=L[p];i<=n;++i){
        ++sum[a[i]];
        if(sum[a[i]]>c||(sum[a[i]]==c&&a[i]<ans))
            ans=a[i],c=sum[a[i]];
        res[p][belong[i]]=ans;
        ci[p][belong[i]]=c;
    }
}

int myfind(int l,int r){
    int p=belong[l],q=belong[r];
    int ress=0,c=0;
    if(p==q){
        for(int i=l;i<=r;++i){
            int hh=upper_bound(v[a[i]].begin(),v[a[i]].end(),r)-1-lower_bound(v[a[i]].begin(),v[a[i]].end(),l)+1;
            if(hh>c||(hh==c&&a[i]<ress))
                c=hh,ress=a[i];
        }
    }
    else{
        if(q-1>=p+1)
            ress=res[p+1][q-1],c=ci[p+1][q-1];
        for(int i=l;i<=R[p];++i){
            int hh=upper_bound(v[a[i]].begin(),v[a[i]].end(),r)-1-lower_bound(v[a[i]].begin(),v[a[i]].end(),l)+1;
            if(hh>c||(hh==c&&a[i]<ress))
                c=hh,ress=a[i];
        }
        for(int i=L[q];i<=r;++i){
            int hh=upper_bound(v[a[i]].begin(),v[a[i]].end(),r)-1-lower_bound(v[a[i]].begin(),v[a[i]].end(),l)+1;
            if(hh>c||(hh==c&&a[i]<ress))
                c=hh,ress=a[i];
        }
    }
    return b[ress];
}


int main(){
    int n,q;
    scanf("%d%d",&n,&q);
    for(int i=1;i<=n;++i){
        scanf("%d",&a[i]);
        b[i]=a[i];
    }
    quchong(n);
    build(n,q);
    int l,r;
    for(int i=1;i<=kuai;++i)
        chuli(i,n);
    int x=0;
    while(q--){
        scanf("%d%d",&l,&r);
        l=(l+x-1)%n+1;
        r=(r+x-1)%n+1;
        if(l>r) swap(l,r);
        x=myfind(l,r);
        printf("%d\n",x);
    }

    return 0;
}

十二、作诗 静态在线求区间出现正偶数次数字的种类数

作诗

预处理f[][]表示块i到块j之间出现偶数次数字的个数。

查询不完整的块的时候,逐个数字判断,两次二分找区间[l,r]内数字出现次数,[ L[p+1],R[q-1] ]区间内数字出现次数,以此来判断对答案的影响。

还没过,咕咕咕。

这份代码只能开O2过洛谷的数据,AcWing还过不掉。

#include<bits/stdc++.h>
using namespace std;

const int maxn=1e5+7;
const int HH=1300;

int f[HH][HH];

int L[HH],R[HH],belong[maxn];
int a[maxn];

vector<int> v[maxn];

int kuai,len;
int sum[maxn];
void build(int n,int q){
    kuai=max(sqrt(n*log2(n)),sqrt(q*log2(n)));
    len=(kuai?n/kuai:n);

    for(int i=1;i<=kuai;++i)
        L[i]=(i-1)*len+1,R[i]=i*len;

    if(R[kuai]<n){
        ++kuai;
        L[kuai]=R[kuai-1]+1;
        R[kuai]=n;
    }

    for(int i=1;i<=kuai;++i)
        for(int j=L[i];j<=R[i];++j){
            belong[j]=i;
            v[a[j]].push_back(j);
        }
}

void chuli(int p,int n){
    memset(sum,0,sizeof(sum));
    int res=0;
    for(int i=L[p];i<=n;++i){
        ++sum[a[i]];
        if(sum[a[i]]>=2){
            if(sum[a[i]]&1) --res;
            else ++res;
        }
        f[p][belong[i]]=res;
    }
}

bool flag[maxn];
int myfind(int l,int r){
    int p=belong[l],q=belong[r];
    int res=0;
    if(p==q||p+1==q){

        for(int i=l;i<=r;++i){
            if(flag[a[i]]) continue;
            int z=upper_bound(v[a[i]].begin(),v[a[i]].end(),r)-lower_bound(v[a[i]].begin(),v[a[i]].end(),l);
            if(!(z&1)) ++res;
            flag[a[i]]=1;
        }
        for(int i=l;i<=r;++i) flag[a[i]]=0;

    }
    else{
        res=f[p+1][q-1];
        int ll=L[p+1],rr=R[q-1];
        for(int i=l;i<=R[p];++i){
            if(flag[a[i]]) continue;
            int z1=upper_bound(v[a[i]].begin(),v[a[i]].end(),rr)-lower_bound(v[a[i]].begin(),v[a[i]].end(),ll);
            int z2=upper_bound(v[a[i]].begin(),v[a[i]].end(),r)-lower_bound(v[a[i]].begin(),v[a[i]].end(),l);
            if(((!z1)||(z1&1))&&(z2%2==0)) ++res;
            else if(z1&&(z1%2==0)&&(z2&1)) --res;
            flag[a[i]]=1;
        }
        for(int i=L[q];i<=r;++i){
            if(flag[a[i]]) continue;
            int z1=upper_bound(v[a[i]].begin(),v[a[i]].end(),rr)-lower_bound(v[a[i]].begin(),v[a[i]].end(),ll);
            int z2=upper_bound(v[a[i]].begin(),v[a[i]].end(),r)-lower_bound(v[a[i]].begin(),v[a[i]].end(),l);
            if(((!z1)||(z1&1))&&(z2%2==0)) ++res;
            else if(z1&&(z1%2==0)&&(z2&1)) --res;
            flag[a[i]]=1;
        }
        for(int i=l;i<=R[p];++i) flag[a[i]]=0;
        for(int i=L[q];i<=r;++i) flag[a[i]]=0;
    }
    return res;
}





int main(){
    int n,m,c;
    scanf("%d%d%d",&n,&c,&m);
    for(int i=1;i<=n;++i) scanf("%d",&a[i]);
    build(n,m);
    for(int i=1;i<=kuai;++i) chuli(i,n);
    int l,r,x=0;

    while(m--){
        scanf("%d%d",&l,&r);
        l=(l+x)%n+1;
        r=(r+x)%n+1;
        if(l>r) swap(l,r);
        x=myfind(l,r);
        printf("%d\n",x);

    }

    return 0;
}

 

 

十三、静态求区间内不同数字的个数或者和

个数与和的不同只是权值的不同,为1或者为本身。

维护f[][]表示从块i到块j内的不同数字的个数或者和。

也是开一个vector存数字出现的位置,像上面那样来判断是否对答案有贡献即可。

HDU - 3333


#include<bits/stdc++.h>
using namespace std;

const int maxn=3e4+7;
const int M=1250;

typedef long long ll;
int a[maxn],b[maxn];
int m;

void quchong(int n){
    sort(b+1,b+1+n);
    m=unique(b+1,b+1+n)-b-1;
}

int getid(int x){
    return lower_bound(b+1,b+1+m,x)-b;
}

vector<int> v[maxn];

ll f[M][M];

int kuai,len;
int L[M],R[M],belong[maxn];

void build(int n,int q){
    for(int i=1;i<=30000;++i) v[i].clear();
    kuai=max(sqrt(q*log2(n)),sqrt(n*log2(n)));
    len=(kuai?n/kuai:n);
    for(int i=1;i<=kuai;++i)
        L[i]=(i-1)*len+1,R[i]=i*len;
    if(R[kuai]<n){
        ++kuai;
        L[kuai]=R[kuai-1]+1;
        R[kuai]=n;
    }

    for(int i=1;i<=kuai;++i)
        for(int j=L[i];j<=R[i];++j){
            a[j]=getid(a[j]);
            v[a[j]].push_back(j);
            belong[j]=i;
        }
}

bool flag[maxn];
void chuli(int p,int n){
    ll z=0;
    for(int i=L[p];i<=n;++i){
        if(!flag[a[i]]) z+=b[a[i]];
        flag[a[i]]=1;
        f[p][belong[i]]=z;
    }
    memset(flag,0,sizeof(flag));
}

ll myfind(int l,int r){
    ll res=0;
    int p=belong[l],q=belong[r];
    if(p==q||p+1==q){
        for(int i=l;i<=r;++i){
            if(!flag[a[i]]) res+=b[a[i]];
            flag[a[i]]=1;
        }
        for(int i=l;i<=r;++i) flag[a[i]]=0;
    }
    else{
        res=f[p+1][q-1];
        int ll=L[p+1],rr=R[q-1];
        for(int i=l;i<=R[p];++i){
            if(flag[a[i]]) continue;
            int z=upper_bound(v[a[i]].begin(),v[a[i]].end(),rr)-lower_bound(v[a[i]].begin(),v[a[i]].end(),ll);
            if(!z) res+=b[a[i]];
            flag[a[i]]=1;
        }
        for(int i=L[q];i<=r;++i){
            if(flag[a[i]]) continue;
            int z=upper_bound(v[a[i]].begin(),v[a[i]].end(),rr)-lower_bound(v[a[i]].begin(),v[a[i]].end(),ll);
            if(!z) res+=b[a[i]];
            flag[a[i]]=1;
        }

        for(int i=l;i<=R[p];++i) flag[a[i]]=0;
        for(int i=L[q];i<=r;++i) flag[a[i]]=0;
    }
    return res;
}

int main(){
    //cout<<(int)(sqrt(100000*log2(30000)))<<endl;
    int t;
    int n,q;
    scanf("%d",&t);
    while(t--){
        scanf("%d",&n);
        for(int i=1;i<=n;++i){
            scanf("%d",&a[i]);
            b[i]=a[i];
        }
        scanf("%d",&q);
        quchong(n);
        int l,r;
        build(n,q);
        for(int i=1;i<=kuai;++i) chuli(i,n);

//        for(int i=1;i<=kuai;++i)
//            for(int j=i;j<=kuai;++j)
//                cout<<L[i]<<" "<<R[j]<<" "<<f[i][j]<<endl;

        while(q--){
            scanf("%d%d",&l,&r);
            printf("%lld\n",myfind(l,r));
        }
    }


    return 0;
}

 

十四、小z的袜子 分块做法

题目链接:

bzoj时限较宽裕:bzoj 2038

分块过不掉AcWing的极强数据,大佬们可以试一试:AcWing 251

这个本来是莫队算法的入门题,我还没学,拿分块怼了一发,过了。

等我会了莫队来补莫队做法,莫队做法

这里先说一下分块做法。

预处理数组 f[][]表示块i到j内选择相同颜色的方案数。

再用一个vector数组记录每个数出现的位置,思路与前几道题一致,二分判断对答案的影响即可。

#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;

const int maxn=5e4+7;
const int HH=900;

int L[HH],R[HH],belong[maxn];
int a[maxn];
int kuai,len;

ll f[HH][HH];

vector<int> v[maxn];

void build(int n,int q){
    kuai=max(sqrt(n*log2(n)),sqrt(q*log2(n)));
    len=(kuai?n/kuai:n);
    for(int i=1;i<=kuai;++i)
        L[i]=(i-1)*len+1,R[i]=i*len;
    if(R[kuai]<n){
        ++kuai;
        L[kuai]=R[kuai-1]+1;
        R[kuai]=n;
    }

    for(int i=1;i<=kuai;++i)
        for(int j=L[i];j<=R[i];++j){
            belong[j]=i;
            v[a[j]].push_back(j);
        }
}

int sum[maxn];

void chuli(int p,int n){

    ll res=0;
    for(int i=L[p];i<=n;++i){
        ++sum[a[i]];
        if(sum[a[i]]>=2){
            res-=(sum[a[i]]-1)*1LL*(sum[a[i]]-2)/2;
            res+=(sum[a[i]])*1LL*(sum[a[i]]-1)/2;
        }
        f[p][belong[i]]=res;
    }
    for(int i=L[p];i<=n;++i) sum[a[i]]=0;
}

bool flag[maxn];
ll myfind(int l,int r){
    int p=belong[l],q=belong[r];
    ll res=0;
    if(p==q||p+1==q){
        for(int i=l;i<=r;++i){
            if(flag[a[i]]) continue;
            int z=upper_bound(v[a[i]].begin(),v[a[i]].end(),r)-lower_bound(v[a[i]].begin(),v[a[i]].end(),l);
            if(z>=2) res+=z*1LL*(z-1)/2;
            flag[a[i]]=1;
        }

        for(int i=l;i<=r;++i)
            flag[a[i]]=0;
    }
    else{
        res+=f[p+1][q-1];
        int ll=L[p+1],rr=R[q-1];

        for(int i=l;i<=R[p];++i){
            if(flag[a[i]]) continue;
            int z1=upper_bound(v[a[i]].begin(),v[a[i]].end(),rr)-lower_bound(v[a[i]].begin(),v[a[i]].end(),ll);
            int z2=upper_bound(v[a[i]].begin(),v[a[i]].end(),r)-lower_bound(v[a[i]].begin(),v[a[i]].end(),l);
            if(z1<=1&&z2>=2) res+=(z2-1)*1LL*z2/2;
            else if(z1>=2&&z2>z1){
                res-=z1*1LL*(z1-1)/2;
                res+=z2*1LL*(z2-1)/2;
            }

            flag[a[i]]=1;
        }
        for(int i=L[q];i<=r;++i){
            if(flag[a[i]]) continue;
            int z1=upper_bound(v[a[i]].begin(),v[a[i]].end(),rr)-lower_bound(v[a[i]].begin(),v[a[i]].end(),ll);
            int z2=upper_bound(v[a[i]].begin(),v[a[i]].end(),r)-lower_bound(v[a[i]].begin(),v[a[i]].end(),l);
            if(z1<=1&&z2>=2) res+=(z2-1)*1LL*z2/2;
            else if(z1>=2&&z2>z1){
                res-=z1*1LL*(z1-1)/2;
                res+=z2*1LL*(z2-1)/2;
            }

            flag[a[i]]=1;
        }


        for(int i=l;i<=R[p];++i) flag[a[i]]=0;
        for(int i=L[q];i<=r;++i) flag[a[i]]=0;
    }



    return res;
}

ll gcd(ll a,ll b){
    return b==0?a:gcd(b,a%b);
}

int main(){
    //cout<<(int)(maxn*sqrt(maxn*log2(maxn)))<<endl;
    int n,q;
    scanf("%d%d",&n,&q);
    for(int i=1;i<=n;++i)
        scanf("%d",&a[i]);
    build(n,q);
    int l,r;
    for(int i=1;i<=kuai;++i)
        chuli(i,n);
    while(q--){
        scanf("%d%d",&l,&r);
        ll x=myfind(l,r);
        ll y=(r-l+1)*1LL*(r-l)/2;
        if(x==0){
            printf("0/1\n");
        }
        else{
            ll z=gcd(x,y);
            printf("%lld/%lld\n",x/z,y/z);
        }
    }



    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值