分块

一、Contest Hunter Round #46 磁力块

描述:
在一片广袤无垠的原野上,散落着N块磁石。每个磁石的性质可以用一个五元组(x,y,m,p,r)描述,其中x,y表示其坐标,m是磁石的质量,p是磁力,r是吸引半径。若磁石A与磁石B的距离不大于磁石A的吸引半径,并且磁石B的质量不大于磁石A的磁力,那么A可以吸引B。
小取酒带着一块自己的磁石L来到了这篇原野的(x0,y0)处,我们可以视为磁石L的坐标为(x0,y0)。小取酒手持磁石L并保持原地不动,所有可以被L吸引的磁石将会被吸引过来。在每个时刻,他可以选择更换任意一块自己已经获得的磁石(当然也可以是自己最初携带的L磁石)在(x0,y0)处吸引更多的磁石。小取酒想知道,他最多能获得多少块磁石呢?
输入格式
第一行五个整数x0,y0,pL,rL,N,表示小取酒所在的位置,磁石L磁力、吸引半径和原野上散落磁石的个数。
接下来N行每行五个整数x,y,m,p,r,描述一块磁石的性质。 输出格式
输出一个整数,表示最多可以获得的散落磁石个数(不包含最初携带的磁石L)。

思路:

容易想到:可以用BFS的思想来统计答案,对于一个磁石,所有他能够吸引的全部加入queue,直到queue为空。

现在我们只需考虑如何快速的判断一块磁石能否被吸引就好了。条件为:质量<=磁力,距离<=吸引半径。

我们考虑分块来解决:

先按质量排序,分成√n 段。然后每一段再按照距离排序。

那么对于每一块用来吸引的磁石(设为x),依次扫描这√n段,假设到第i段时,这一段的mmax大于R[x],停止。

此时,

对于前i-1段,必定满足第一个条件,对于这些段的,直接从左往右扫,根据距离关系判断一下就好,那么复杂度均摊O(1),有i-1块则总复杂度为O(√n)。

对于第i段,暴力扫描整段,满足条件的加入答案并打上标记(避面重复),复杂度O(√n)。

所以,整个算法复杂度为O(n√n)。

 

#include<bits/stdc++.h>
#define RG register
#define IL inline 
using namespace std;

IL int gi () {
    RG int x=0,w=0; char ch=0;
    while (ch<'0'||ch>'9') {if (ch=='-') w=1;ch=getchar();}
    while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
    return w?-x:x;
}

const int N=250010;
const int Sqn=510;
const double eps=1e-6;

int n,Num,len,dir,ans;

struct STONE {
    double R,dis; 
    int x,y,m,p,vis;
}sto[N];
struct BLOCK {
    STONE s[Sqn];
    int head,num,Max;
}blo[Sqn];
IL double calc (STONE a,STONE b) {
    return (double)sqrt(1.0*(a.x-b.x)*(a.x-b.x)+1.0*(a.y-b.y)*(a.y-b.y));
}
IL bool cmp1 (STONE a,STONE b) {return a.m<b.m;}
IL bool cmp2 (STONE a,STONE b) {return  a.dis<b.dis;}

queue <STONE> q;

int main ()
{
    double R;
    RG int i,j,x,y,m,p;
    x=gi(),y=gi(),p=gi();scanf ("%lf",&R);
    sto[0]=(STONE){R,0,x,y,0,p,0};
    n=gi(),len=sqrt(n),Num=n/len+(n%len!=0);
    for (i=1;i<=n;++i) {
        x=gi(),y=gi(),m=gi(),p=gi();scanf ("%lf",&R);
        sto[i]=(STONE){R,0,x,y,m,p,0};
        sto[i].dis=calc(sto[i],sto[0]);
    }
    sort (sto+1,sto+n+1,cmp1);
    for (i=1;i<=Num;++i) {
        dir=(i-1)*len;
        blo[i].head=1,blo[i].num=0;
        for (j=1;j<=len&&dir+j<=n;++j) 
            blo[i].s[++blo[i].num]=sto[dir+j];   
        blo[i].Max=blo[i].s[blo[i].num].m;
        sort (blo[i].s+1,blo[i].s+blo[i].num+1,cmp2);
    }
    q.push(sto[0]);
    while (!q.empty()) {
        RG STONE now=q.front(); q.pop();
        for (i=1;i<=Num&&now.p>=blo[i].Max;++i)
            while (now.R+eps>=blo[i].s[blo[i].head].dis&&blo[i].head<=blo[i].num) {
                if (!blo[i].s[blo[i].head].vis) 
                    ++ans,blo[i].s[blo[i].head].vis=1,q.push(blo[i].s[blo[i].head]);
                ++blo[i].head;
            }
        dir=i;
        if (dir>Num) continue; 
        for (i=blo[dir].head;i<=blo[dir].num;++i)
            if (blo[dir].s[i].vis==0&&now.p>=blo[dir].s[i].m&&now.R+eps>=blo[dir].s[i].dis)
            ++ans,blo[dir].s[i].vis=1,q.push(blo[dir].s[i]);
    }
    printf ("%d\n",ans);
    return 0;
}
BY BHLLX

 二、LuoGu P4186 蒲公英

一句话题意:在线求区间众数。

这里给出的是一个暴力些的做法:

设整个数列分成T段。

先统计好第i段到第j段每个数出现的个数cnt[i][j][col]和众数f[i][j],时空复杂度为O(nT2)。

对于每个询问,朴素扫描两端的每一个数,累加到cnt[i][j][col]中,同时更新答案,最后再扫一遍还原就好。

m次询问,所以时间复杂度为O(mn/T)。

所以总时间复杂度为O(nT2+mn/T),空间复杂度为O(nT2)。

根据均值不等式,可以知道T≈3√n时,整个算法复杂度最低,为O(n5/3),可以接受。

#include<bits/stdc++.h>
#define RG register
#define IL inline 
using namespace std;

IL int gi () {
    RG int x=0,w=0; char ch=0;
    while (ch<'0'||ch>'9') {if (ch=='-') w=1;ch=getchar();}
    while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
    return w?-x:x;
}

const int N=40010;
const int INF=0x3f3f3f3f;

int f[41][41],dir[41][41];
int n,m,Num,tot,len,ans,fa[N],typ[N],mp[N],col[N],a[N],blo[N],L[41],R[41],cnt[41][41][N];

IL int Query (int Ql,int Qr) {
    RG int i,Max=0,mans=INF,Lp=blo[Ql],Rp=blo[Qr];
    if (Rp-Lp<=1) {
        for (i=Ql;i<=Qr;++i) {
            RG int num=++col[mp[i]];
            if (num>Max||(num==Max&&a[i]<mans))
                Max=num,mans=a[i];
        }
        for (i=Ql;i<=Qr;++i) --col[mp[i]];
    }
    else {
        Max=f[Lp+1][Rp-1],mans=dir[Lp+1][Rp-1];
        for (i=Ql;i<=R[Lp];++i) {
            RG int tt=mp[i];
            RG int num=++cnt[Lp+1][Rp-1][tt];
            if (num>Max||(num==Max&&a[i]<mans))
                Max=num,mans=a[i];
        }
        for (i=L[Rp];i<=Qr;++i) {
            RG int tt=mp[i];
            RG int num=++cnt[Lp+1][Rp-1][tt];
            if (num>Max||(num==Max&&a[i]<mans))
                Max=num,mans=a[i];
        }
        for (i=Ql;i<=R[Lp];++i) --cnt[Lp+1][Rp-1][mp[i]];
        for (i=L[Rp];i<=Qr;++i) --cnt[Lp+1][Rp-1][mp[i]];
    }
    return mans;
}

int main ()
{
    RG int i,j,k,l,r;
    n=gi(),m=gi();
    Num=(int)pow (n*1.0,1.0/3.0),len=n/Num;
    for (i=1;i<=n;++i) fa[i]=a[i]=gi();
    sort(fa+1,fa+1+n);
    for (i=1;i<=n;++i)
        if (i==1||fa[i]!=fa[i-1]) typ[++tot]=fa[i];
    for (i=1;i<=n;++i) mp[i]=lower_bound (typ+1,typ+1+tot,a[i])-typ;
    for (i=1;i<=Num;++i) {
        L[i]=(i-1)*len+1,R[i]=i*len;
        for (j=L[i];j<=R[i];++j) blo[j]=i;
    }
    if (R[Num]<n) {
        ++Num;
        L[Num]=R[Num-1]+1,R[Num]=n;
        for (i=L[Num];i<=R[Num];++i) blo[i]=Num;
    }
    for (i=1;i<=Num;++i)
        for (j=i;j<=Num;++j) {
            for (k=L[i];k<=R[j];++k) ++cnt[i][j][mp[k]];
            for (k=1;k<=tot;++k)
                if(f[i][j]<cnt[i][j][k]||(f[i][j]==cnt[i][j][k]&&dir[i][j]>typ[k]))
                    f[i][j]=cnt[i][j][k],dir[i][j]=typ[k];
        }
    while (m--) {
        l=gi(),r=gi();
        l=(l+ans-1)%n+1,r=(r+ans-1)%n+1;
        if (l>r) swap (l,r);
        printf ("%d\n",ans=Query (l,r));
    }
    return 0;
}
BY BHLLX

 三、LuoGu P4135作诗

一句话题意:在线求区间出现正偶数次的数的个数。

处理方法:

预处理出f[i][j]表示第i块到第j块的答案,cnt[i][j]表示前i块中颜色为j的出现的次数(类似前缀和)。

然后对于每一个询问,能够影响答案的只有两边多余出来的部分。所以只需暴力扫描这些点,利用cnt[][]来算贡献就好了。复杂度O(n√n)。

#include<bits/stdc++.h>
#define RG register
#define IL inline 
using namespace std;

IL int gi () {
    RG int x=0,w=0; char ch=0;
    while (ch<'0'||ch>'9') {if (ch=='-') w=1;ch=getchar();}
    while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
    return w?-x:x;
}

const int N=1e5+10;
const int Sqn=410;

int n,m,c,s,num,ans,len,a[N],C[Sqn][N],f[Sqn][Sqn];
int blo[N],L[Sqn],R[Sqn],cnt[N],col[N];

IL int query (int l,int r) {
    RG int i,ans=0,tot,res,Lb=blo[l],Rb=blo[r];
    if (Rb-Lb<=1) {
        for (i=l;i<=r;++i) {
            tot=++col[a[i]];
            if (tot==1) continue;
            (tot&1)?--ans:++ans;
        }
        for (i=l;i<=r;++i) --col[a[i]];
    }
    else {
        ans=f[Lb+1][Rb-1];
        for (i=l;i<=R[Lb];++i) {
            tot=++col[a[i]];
            res=tot+C[Rb-1][a[i]]-C[Lb][a[i]];
            if (res==1) continue;
            (res&1)?--ans:++ans;
        }
        for (i=L[Rb];i<=r;++i) {
            tot=++col[a[i]];
            res=tot+C[Rb-1][a[i]]-C[Lb][a[i]];
            if (res==1) continue;
            (res&1)?--ans:++ans;
        }
        for (i=l;i<=R[Lb];++i) --col[a[i]];
        for (i=L[Rb];i<=r;++i) --col[a[i]];
    }
    return ans;
}

int main ()
{
    RG int i,j,k,now,l,r;
    n=gi(),c=gi(),m=gi();
    len=sqrt(n)+1,num=n/len+(n%len!=0);
    for (i=1;i<=num;++i) {
        L[i]=R[i-1]+1,R[i]=(i==num)?n:i*len;
        for (j=L[i];j<=R[i];++j) blo[j]=i;
    }
    for (i=1;i<=n;++i) ++C[blo[i]][a[i]=gi()];
    for (i=2;i<=num;++i)
        for (j=1;j<=c;++j)
            C[i][j]+=C[i-1][j];
    for (i=1;i<=num;++i) {
        k=0;
        for (j=L[i];j<=n;++j) {
            now=++cnt[a[j]];
            (now&1)?--k:++k;
            if (now==1) ++k;
            f[i][blo[j]]=k;
        }
        for (j=L[i];j<=n;++j) --cnt[a[j]];
    }
    while (m--) {
        l=gi(),r=gi();
        l=(l+ans)%n+1,r=(r+ans)%n+1;
        if (l>r) l^=r,r^=l,l^=r;
        printf ("%d\n",ans=query(l,r));
    }
    return 0;
}
BY BHLLX

 四、LuoGu P2801 教主的魔法

描述:

教主最近学会了一种神奇的魔法,能够使人长高。于是他准备演示给XMYZ信息组每个英雄看。于是N个英雄们又一次聚集在了一起,这次他们排成了一列,被编号为1、2、……、N。

每个人的身高一开始都是不超过1000的正整数。教主的魔法每次可以把闭区间[L, R](1≤L≤R≤N)内的英雄的身高全部加上一个整数W。(虽然L=R时并不符合区间的书写规范,但我们可以认为是单独增加第L(R)个英雄的身高)

CYZ、光哥和ZJQ等人不信教主的邪,于是他们有时候会问WD闭区间 [L, R] 内有多少英雄身高大于等于C,以验证教主的魔法是否真的有效。

WD巨懒,于是他把这个回答的任务交给了你。

思路:

容易想到区间加可以用分块维护,重点是分块后如何统计答案。

注意到区间加这个操作并不影响 一个完全被包含的块内的各个数之间的大小关系,因此我们考虑给每个块排序。

对于区间加,只需把两边多余的所在的块重新排序;对于询问,两边多余的暴力统计,中间的块二分查找一下就好了。

因此可以在O(q√n*log√n)内完成。值得注意的是,由于排了序,所以原位置被打乱,对于两边的统计需要扫描整个两边的块,然后根据位置计算贡献。

#include<bits/stdc++.h>
#define RG register
#define IL inline 
using namespace std;

IL int gi () {
    RG int x=0,w=0; char ch=0;
    while (ch<'0'||ch>'9') {if (ch=='-') w=1;ch=getchar();}
    while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
    return w?-x:x;
}

const int N=1e6+10;
const int sqN=1e3+10;

char opt[2];
int L[sqN],R[sqN],tag[sqN];
int n,q,len,num,bel[N];

struct BLOCKS {int id,high;}blo[N];
IL bool cmp (BLOCKS A,BLOCKS B) {return A.high>B.high;}

IL int search (int id,int x) {
    RG int l=L[id],r=R[id],mid;
    while (l<r) {
        mid=(l+r+1)>>1;
        if (blo[mid].high+tag[id]>=x) l=mid;
        else r=mid-1;
    }
    if (l==L[id]&&blo[l].high+tag[id]<x) return 0;
    return l-L[id]+1;
}

void modify (int l,int r,int x) {
    RG int i,Lb=bel[l],Rb=bel[r];
    if (Lb==Rb) {
        for (i=L[Lb];i<=R[Lb];++i)
            if (blo[i].id>=l&&blo[i].id<=r) blo[i].high+=x;
        return;
    }
    for (i=L[Lb];i<=R[Lb];++i)
        if (blo[i].id>=l) blo[i].high+=x;
    for (i=L[Rb];i<=R[Rb];++i)
        if (blo[i].id<=r) blo[i].high+=x;
    sort (blo+L[Lb],blo+R[Lb]+1,cmp);
    sort (blo+L[Rb],blo+R[Rb]+1,cmp);
    for (i=Lb+1;i<=Rb-1;++i) tag[i]+=x;
}

int query (int l,int r,int x) {
    RG int i,Lb=bel[l],Rb=bel[r],ans=0;
    if (Lb==Rb) {
        for (i=L[Lb];i<=R[Lb];++i)
            if (blo[i].id>=l&&blo[i].id<=r) ans+=(blo[i].high+tag[Lb]>=x);
        return ans;
    }
    for (i=L[Lb];i<=R[Lb];++i)
        if (blo[i].id>=l) ans+=(blo[i].high+tag[Lb]>=x);
    for (i=L[Rb];i<=R[Rb];++i)
        if (blo[i].id<=r) ans+=(blo[i].high+tag[Rb]>=x);
    for (i=Lb+1;i<=Rb-1;++i) ans+=search(i,x);
    return ans;
}

int main ()
{
    RG int i,l,r,x;
    n=gi(),q=gi(),len=sqrt(n),num=n/len+(n%len!=0);
    for (i=1;i<=n;++i) blo[i].id=i,blo[i].high=gi(),bel[i]=(i-1)/len+1;
    for (i=1;i<=num;++i) L[i]=R[i-1]+1,R[i]=i*len; R[num]=n;
    for (i=1;i<=num;++i) sort(blo+L[i],blo+R[i]+1,cmp);
    while (q--) {
        scanf ("%s",opt+1),l=gi(),r=gi(),x=gi();
        if (opt[1]=='M') modify(l,r,x);
        else printf ("%d\n",query(l,r,x));
    }
    return 0;
}
BY BHLLX

 

posted @ 2018-12-15 22:11  薄荷凉了夏  阅读(195)  评论(0编辑  收藏  举报