分块
一、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; }
二、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; }
三、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; }
四、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; }