BZOJ2821 作诗,区间[L,R]出现正偶数次的个数,题目要求必须在线。对于这种防莫队算法题,那也是相当无语。好吧,上分块。首先如何能高效查询一段区间内某个数字出现多少次,暴力就是扫一遍O(n),不那么暴力,我们可以将所有数字从小到大排序,那么同一个数显然是一段连续区间,我们扫一遍,记录最左和最右的那个数在原数组的下标,那么对于L和R分别二分,然后再减一下,不就知道个数了,利用这一点,我们块内就这么暴力,块之间需要预处理f[i][j]表示第i块到第j块出现正偶数次的数字个数,然后考虑端点两块对这中间块的影响,仍然利用上面二分方法,查询某个数组在[L,R]和中间块出现的次数,然后就可以考虑加减1的问题了。具体见代码。
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#define Maxn 100010
#define Maxm 1300
using namespace std;
inline int read(){
int x=0;
char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9'){
x=x*10+ch-'0';
ch=getchar();
}
return x;
}
int n,m,c,qt,sz;
int pos[Maxn],a[Maxn];
int L[Maxm],R[Maxm];
void init(){
sz=sqrt(n/log(n)*log(2));
m=(n+sz-1)/sz;
for(int i=0;i<n;i++) pos[i]=i/sz;
for(int i=0;i<m;i++) L[i]=i*sz,R[i]=L[i]+sz-1;
R[m-1]=n-1;
}
int f[Maxm][Maxm],cl[Maxn],p[Maxn];
int fst[Maxn],lst[Maxn];
bool cmp(int i,int j){
return a[i]<a[j]||a[i]==a[j]&&i<j;
}
void pre(){
for(int i=0;i<m;i++){ //预处理块i到块j
int cnt=0;
for(int j=L[i];j<n;j++){
cl[a[j]]++;
if(!(cl[a[j]]&1)) cnt++; //偶数
else if(cl[a[j]]>2) cnt--; //奇数大于2
f[i][pos[j]]=cnt;
}
for(int j=L[i];j<n;j++) cl[a[j]]=0;
}
for(int i=0;i<n;i++) p[i]=i;
for(int i=1;i<=n;i++) fst[i]=-1;
sort(p,p+n,cmp); //按照a[i]排序
for(int i=0;i<n;i++){ //预处理每种颜色起始和终止位置
if(fst[a[p[i]]]<0) fst[a[p[i]]]=i;
lst[a[p[i]]]=i;
}
}
int vis[Maxn];
int ck(int l,int r,int x){ //查询区间[l,r]颜色为x的数量
int len=upper_bound(p+fst[x],p+lst[x]+1,r)-
lower_bound(p+fst[x],p+lst[x]+1,l);
return max(0,len);
}
void cal(int i,int l,int r,int &ans){
if(vis[a[i]]) return;
int c1=ck(l,r,a[i]),c2=ck(R[pos[l]]+1,L[pos[r]]-1,a[i]);
vis[a[i]]=1;
if((c2&1)||!c2){ //原先奇数或0
if(!(c1&1)) ans++; //现在偶数
}
else{ //原先偶数且大于0
if(c1&1) ans--; //现在奇数
}
}
int query(int l,int r){
int ans=0;
if(pos[l]==pos[r]||pos[l]+1==pos[r]){
for(int i=l;i<=r;i++){
if(vis[a[i]]) continue;
int cnt=ck(l,r,a[i]);
if(!(cnt&1)&&cnt>1) ans++;
vis[a[i]]=1;
}
for(int i=l;i<=r;i++) vis[a[i]]=0; //清0
}
else{
ans=f[pos[l]+1][pos[r]-1]; //按块处理
for(int i=l;i<=R[pos[l]];i++) cal(i,l,r,ans);
for(int i=L[pos[r]];i<=r;i++) cal(i,l,r,ans);
for(int i=l;i<=R[pos[l]];i++) vis[a[i]]=0;
for(int i=L[pos[r]];i<=r;i++) vis[a[i]]=0;
}
return ans;
}
int main()
{
int l,r;
n=read(),c=read(),qt=read();
init();
for(int i=0;i<n;i++) a[i]=read();
pre();
int ans=0;
for(int i=0;i<qt;i++){
l=read(),r=read();
l=(l+ans)%n,r=(r+ans)%n;
if(l>r) swap(l,r);
ans=query(l,r);
printf("%d\n",ans);
}
return 0;
}
BZOJ3809 询问区间[L,R]内[a,b]区间中的数出现了多少次,终于可以离线了。上莫队大法,很容易想到利用树状数组来维护,但是这么做修改复杂度将是O(nsqrt(n)log(n)),不怎么好,于是我们知道每个数字都是[1,n],那么对数字分块吧,修改O(1),查询O(sqrt(n)),最后复杂度主要是询问O(msqrt(n)),稍好一点,当然块大小取sqrt(n/2),常数更小一点。
上代码:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#define Maxn 100010
using namespace std;
inline int read(){
int x=0;
char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9'){
x=x*10+ch-'0';
ch=getchar();
}
return x;
}
int pos[Maxn];
struct query{
int l,r,a,b,id;
void Read(){
l=read(),r=read(),a=read(),b=read();
l--,r--,a--,b--;
}
bool operator<(const query &c)const{
return pos[l]<pos[c.l]||pos[l]==pos[c.l]&&pos[r]<pos[c.r];
}
}q[Maxn*10];
int n,m,qt,sz;
void init(){
sz=sqrt(n/2);
m=(n+sz-1)/sz;
for(int i=0;i<n;i++) pos[i]=i/sz;
}
int a[Maxn],c[Maxn],b[Maxn];
void add(int x){
if(++c[a[x]]==1) b[pos[a[x]]]++;
}
void del(int x){
if(--c[a[x]]==0) b[pos[a[x]]]--;
}
int query(int l,int r){
int ans=0;
if(pos[l]==pos[r]){
for(int i=l;i<=r;i++) ans+=c[i]!=0;
}
else{
for(int i=pos[l]+1;i<pos[r];i++) ans+=b[i];
for(int i=l;i<(pos[l]+1)*sz;i++) ans+=c[i]!=0;
for(int i=pos[r]*sz;i<=r;i++) ans+=c[i]!=0;
}
return ans;
}
int res[Maxn*10];
int main()
{
n=read(),qt=read();
init();
for(int i=0;i<n;i++) {a[i]=read();a[i]--;}
for(int i=0;i<qt;i++){
q[i].Read();
q[i].id=i;
}
sort(q,q+qt);
int l=0,r=-1;
for(int i=0;i<qt;i++){
while(r<q[i].r) add(++r);
while(r>q[i].r) del(r--);
while(l>q[i].l) add(--l);
while(l<q[i].l) del(l++);
res[q[i].id]=query(q[i].a,q[i].b);
}
for(int i=0;i<qt;i++)
printf("%d\n",res[i]);
return 0;
}
BZOJ3289 区间逆序对,莫队+树状数组,非常简单...注意离散化
代码:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#define Maxn 50010
using namespace std;
inline int read(){
int x=0;
char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9'){
x=x*10+ch-'0';
ch=getchar();
}
return x;
}
int pos[Maxn];
struct query{
int l,r,id;
void Read(){
l=read(),r=read();
l--,r--;
}
bool operator<(const query &c)const{
return pos[l]<pos[c.l]||pos[l]==pos[c.l]&&pos[r]<pos[c.r];
}
}q[Maxn];
int n,m,qt,sz;
void init(){
sz=sqrt(n);
m=(n+sz-1)/sz;
for(int i=0;i<n;i++) pos[i]=i/sz;
}
int a[Maxn],b[Maxn],c[Maxn];
void add(int x,int y){
for(int i=x;i<=n;i+=i&-i)
c[i]+=y;
}
int sum(int x){
int ans=0;
for(int i=x;i;i-=i&-i)
ans+=c[i];
return ans;
}
int res[Maxn];
void query(){
int ans=0;
int l=0,r=-1;
for(int i=0;i<qt;i++){
while(r<q[i].r){
r++;
ans+=sum(n)-sum(a[r]);
add(a[r],1);
}
while(r>q[i].r){
ans-=sum(n)-sum(a[r]);
add(a[r],-1);
r--;
}
while(l>q[i].l){
l--;
ans+=sum(a[l]-1);
add(a[l],1);
}
while(l<q[i].l){
ans-=sum(a[l]-1);
add(a[l],-1);
l++;
}
res[q[i].id]=ans;
}
}
int main()
{
n=read();
init();
for(int i=0;i<n;i++) {a[i]=b[i]=read();}
sort(b,b+n);
for(int i=0;i<n;i++)
a[i]=lower_bound(b,b+n,a[i])-b+1;
qt=read();
for(int i=0;i<qt;i++){
q[i].Read();
q[i].id=i;
}
sort(q,q+qt);
query();
for(int i=0;i<qt;i++)
printf("%d\n",res[i]);
return 0;
}