分块 / 莫队乱写 (咕咕咕了五个月)

分块/莫队乱写

前情提要:这是一篇时间跨度长达五个月的博客,知道今天水博的时候才发现这篇一直写同时一直咕咕咕的博文。整篇博客的文字和代码风格差异可能很大,请谨慎食用。

分块

概念

分块是一种思想,把一个整体划分为若干个小块,对整块整体处理,零散块单独处理。本文主要介绍块状数组——利用分块思想处理区间问题的一种数据结构。

块状数组把一个长度为 \(n\) 的数组划分为 \(a\) 块,每块长度为 \(n/a\)。对于一次区间操作,对区间内部的整块进行整体的操作,对区间边缘的零散块单独暴力处理。(所以分块被称为“优雅的暴力”)。

这里,块数既不能太少也不能太多。如果太少,区间中整块的数量会很少,我们要花费大量时间处理零散块;如果太多,又会让块的长度太短,失去整体处理的意义。一般来说,我们取块数为 \(\sqrt{n}\),这样在最坏情况下,我们要处理接近 \(\sqrt{n}\) 个整块,还要对长度为 \(2 \times \sqrt{n}\) 的零散块(最后一块可能不完整,并入划分的倒数第二块中,作为一个整体,因此最大长度为 \(2 \times \sqrt{n}\) )单独处理,总时间复杂度为 \(O(\sqrt{n})\) 。这是一种根号算法。

特点

  1. 根号算法,效率较高

  2. 相较于线段树,更灵活,块状数组不要求所维护信息满足结合律,也不需要一层层地传递标记

实现

  1. 划分块:

    一般块数 \(num\) 设为 \(\sqrt{n}\) 。末尾的散块归入最后一整块。

    一般,第 \(i\) 块的起点为 \(n / num \times (i - 1) + 1\) ,终点为 \(n / num \times i\) 。(最后一块除外)。

  2. 处理元素归属,同时预处理诸如标记等的数组。

题目

P3203 [HNOI2010]弹飞绵羊

用块状数组维护当前点跳几次能跳出当前块,以及维护跳出当前块之后会落到哪。

\(step\) 数组来维护要跳几次, \(to\) 数组维护跳到哪里。

需要从后往前维护。

时间复杂度 \(O(n \sqrt{n})\)

Code

点击查看代码
#include<cmath>
#include<cstdio>

using namespace std;

const int MAXN=2e5+10;
int n,m;
int equip[MAXN];
int st[MAXN],ed[MAXN],belong[MAXN],siz[MAXN];
int to[MAXN],step[MAXN];

inline int read(){
	int x=0,f=1;
	char c=getchar();
	
	while(c<'0' || c>'9'){
		if(c=='-') f=-1;
		c=getchar();
	}
	while(c>='0' && c<='9'){
		x=(x<<1)+(x<<3)+(c^48);
		c=getchar();
	}
	
	return x*f;
}

void init(int x){
	int sq=sqrt(x);
	
	for(register int i=1;i<=sq;i++){
		st[i]=x/sq*(i-1)+1;
		ed[i]=x/sq*i;
	}
	ed[sq]=x;
	
	for(register int i=1;i<=sq;i++){
		for(register int j=st[i];j<=ed[i];j++)
			belong[j]=i;
		siz[i]=ed[i]-st[i]+1;
	}
	
	for(register int i=x;i>=1;i--){
		to[i]=i+equip[i];
		
		if(to[i]>ed[belong[i]])
			step[i]=1;
		else{
			step[i]=step[to[i]]+1;
			to[i]=to[to[i]];
		}
	}
}

void update(int pos,int data){
	equip[pos]=data;
	for(register int i=ed[belong[pos]];i>=st[belong[pos]];i--){
		to[i]=i+equip[i];
		
		if(to[i]>ed[belong[i]])
			step[i]=1;
		else{
			step[i]=step[to[i]]+1;
			to[i]=to[to[i]];
		}
	}
}

int query(int pos){
	int ans=0;
	
	while(pos<=n){
		ans+=step[pos];
		pos=to[pos];
	}
	
	return ans;
}

int main(){
	n=read();
	for(register int i=1;i<=n;i++)
		equip[i]=read();
	init(n);
	m=read();
	for(register int i=1;i<=m;i++){
		int num,y;
		num=read(),y=read();
		y++;
		if(num==1)
			printf("%d\n",query(y));
			
		else if(num==2){
			int k;
			k=read();
			update(y,k);
		}
	}
	
	return 0;
}

P4168 [Violet]蒲公英

正解是用块状数组维护区间众数,但我不会。

整了一个离散化后给每个块开一个桶,然后再和整体的桶合并。

最后玄学卡块长过了。

Code

点击查看代码
#include<cmath>
#include<cstdio>
#include<algorithm>

using namespace std;

const int MAX=40010;
const int SIZE=410;
int n,m,cnt,last;
int dandelion[MAX];
int st[MAX],ed[MAX],belong[MAX];
int tmp[MAX],val[MAX];
int temp[MAX],num[SIZE][MAX];

inline int read(){
	int x=0,f=1;
	char c=getchar();

	while(c<'0' || c>'9'){
		if(c=='-') f=-1;
		c=getchar();
	}
	while(c>='0' && c<='9'){
		x=(x<<1)+(x<<3)+(c^48);
		c=getchar();
	}

	return x*f;
}

inline void write(int x){
	if(x==0){
		putchar('0');
	}
	else{
		int top=0,stk[35];
		while(x){
			stk[++top]=x%10;
			x/=10;
		}
		do{
			putchar(stk[top--]+48);
		}while(top);
	}
	
	putchar('\n');
}

inline void swap_own(int &a,int &b){
	a^=b,b^=a,a^=b;
}

inline void min_own(int &a,int &b){
	if(a>b) a=b;
}

inline void Riddle(int &l,int &r,int x){
	int st,ed;
	st=(l+x-1)%n+1;
	ed=(r+x-1)%n+1;
	if(st>ed) swap_own(st,ed);
	l=st;
	r=ed;
}

void init(int x){
	int sq;
	if(x<=20000) sq=sqrt(x);
	else sq=x>>12;

	for(register int i=1;i<=sq;i++){
		st[i]=x/sq*(i-1)+1;
		ed[i]=x/sq*i;
	}
	ed[sq]=x;

	for(register int i=1;i<=sq;i++){
		for(register int j=st[i];j<=ed[i];j++){
			num[i][dandelion[j]]++; 
			belong[j]=i;
		}
	}
}

int query(int l,int r){
	for(register int i=1;i<=cnt;i++)
		temp[i]=0;
	int ans=0,violet;

	if(belong[l]==belong[r]){
		for(register int i=l;i<=r;i++)
			temp[dandelion[i]]++;
		for(register int i=l;i<=r;i++){
			if(temp[dandelion[i]]>ans){
				ans=temp[dandelion[i]];
				violet=val[dandelion[i]];
			}
			else if(temp[dandelion[i]]==ans){
				min_own(violet,val[dandelion[i]]);
			}
		}
	}
	else{
		for(register int i=l;i<=ed[belong[l]];i++)
			temp[dandelion[i]]++;
		for(register int i=st[belong[r]];i<=r;i++)
			temp[dandelion[i]]++;
		for(register int i=belong[l]+1;i<belong[r];i++){
			for(register int j=1;j<=cnt;j++)
				temp[j]+=num[i][j];
		}
		for(register int i=1;i<=cnt;i++){
			if(temp[i]>ans){
				ans=temp[i];
				violet=val[i];
			}
			else if(temp[i]==ans){
				min_own(violet,val[i]);
			}
		}
	}
	
	return violet;
}

int main(){
	n=read(),m=read();
	for(register int i=1;i<=n;i++){
		dandelion[i]=read();
		tmp[i]=dandelion[i];
	}

	sort(tmp+1,tmp+1+n);
	cnt=unique(tmp+1,tmp+n+1)-tmp-1;
	for(register int i=1;i<=n;i++){
		int pos=lower_bound(tmp+1,tmp+1+cnt,dandelion[i])-tmp;
		val[pos]=dandelion[i];
		dandelion[i]=pos;
	}//离散化

	init(n);

	for(register int i=1;i<=m;i++){
		int l,r;
		l=read(),r=read();
		if(i==1)
            Riddle(l,r,0);
        else
            Riddle(l,r,last);
        last=query(l,r);
		write(last);
	}

	return 0;
}

P2801 教主的魔法

分块 + 二分。

这道题算是让我明白了什么是 暴力 + 暴力 = 分块。

涉及区间修改操作的话可以给整块打上标记,散块就直接暴力修改。

在块上二分需要开一个辅助数组,就是代码里的 \(block\) 数组。

\(block\) 数组上对每个块的范围 \(sort\) 一遍,之后二分答案,找块内第一个小于 \(C\) 的数。

注意在 \(update\) 之后要更新 \(block\) 数组,同时要 \(sort\)

Code

点击查看代码
#include<cmath>
#include<cstdio>
#include<algorithm>

using namespace std;

const int MAXN=1000010;
const int SIZE=1010;
int n,q;
int hight[MAXN];
int st[SIZE],ed[SIZE],belong[MAXN],mark[SIZE],block[MAXN];

inline int read(){
	int x=0,f=1;
	char c=getchar();
	
	while(c<'0' || c>'9'){
		if(c=='-') f=-1;
		c=getchar();
	}
	while(c>='0' && c<='9'){
		x=(x<<1)+(x<<3)+(c^48);
		c=getchar();
	}
	
	return x*f;
}

void init(int x){
	int sq=sqrt(x);
	
	for(register int i=1;i<=sq;i++){
		st[i]=x/sq*(i-1)+1;
		ed[i]=x/sq*i;
	}
	ed[sq]=x;
	
	for(register int i=1;i<=sq;i++){
		for(register int j=st[i];j<=ed[i];j++){
			belong[j]=i;
			block[j]=hight[j];
		}
		sort(block+st[i],block+ed[i]+1);
	}
}

void update(int l,int r,int data){
	if(belong[l]==belong[r]){
		for(register int i=l;i<=r;i++)
			hight[i]+=data;
		for(register int i=st[belong[l]];i<=ed[belong[r]];i++)
			block[i]=hight[i];
		sort(block+st[belong[l]],block+ed[belong[r]]+1);
	}
	else{
		for(register int i=l;i<=ed[belong[l]];i++)
			hight[i]+=data;
		for(register int i=st[belong[l]];i<=ed[belong[l]];i++)
			block[i]=hight[i];
		sort(block+st[belong[l]],block+ed[belong[l]]+1);
		for(register int i=st[belong[r]];i<=r;i++)
			hight[i]+=data;
		for(register int i=st[belong[r]];i<=ed[belong[r]];i++)
			block[i]=hight[i];
		sort(block+st[belong[r]],block+ed[belong[r]]+1);
		for(register int i=belong[l]+1;i<belong[r];i++)
			mark[i]+=data;
	}
} 

int query(int l,int r,int k){
	int ans=0;
	
	if(belong[l]==belong[r]){
		for(register int i=l;i<=r;i++)
			if(hight[i]+mark[belong[l]]>=k) ans++;
	}
	else{
		for(register int i=l;i<=ed[belong[l]];i++)
			if(hight[i]+mark[belong[l]]>=k) ans++;
		for(register int i=st[belong[r]];i<=r;i++)
			if(hight[i]+mark[belong[r]]>=k) ans++;
		for(register int i=belong[l]+1;i<belong[r];i++){
			int le=st[i],ri=ed[i],siz=0;
			while(le<=ri){
				int mid=(le+ri)>>1;
				if(block[mid]+mark[i]>=k){
					siz=ed[i]-mid+1;
					ri=mid-1;
				}
				else le=mid+1;
			}
			ans+=siz;
		}
	}
	
	return ans;
}

int main(){
	n=read(),q=read();
	for(register int i=1;i<=n;i++)
		hight[i]=read();
		
	init(n);
	
	for(register int i=1;i<=q;i++){
		char k[1];
		scanf("%s",k);
		if(k[0]=='M'){
			int l,r,w;
			l=read(),r=read(),w=read();
			update(l,r,w);
		}
		else if(k[0]=='A'){
			int l,r,c;
			l=read(),r=read(),c=read();
			printf("%d\n",query(l,r,c));
		}
	}
	
	return 0;
}

P1903 [国家集训队] 数颜色 / 维护队列

好像分块 + 二分可搞。

但我拿带修莫队写的,过。

P2464 [SDOI2008] 郁闷的小 J

正解不是分块,但分块吸氧后能过。

还是分块加二分的老套路。

需要注意的是,在更新一点后,要给 \(block\) 数组重新复制。

Code

点击查看代码
#include<cmath>
#include<cstdio>
#include<algorithm>

using namespace std;

const int MAXN = 1e5 + 10;
int n, m;
int book[MAXN];
int st[MAXN], ed[MAXN], belong[MAXN], block[MAXN];

void init(register int x){
    register int sq = sqrt(x);

    for(register int i = 1; i <= sq; i++){
        st[i] = x / sq * (i - 1) + 1;
        ed[i] = x / sq * i;
    }
    ed[sq] = x;

    for(register int i = 1; i <= sq; i++){
        for(register int j = st[i]; j <= ed[i]; j++){
            belong[j] = i;
            block[j] = book[j];
        }
        sort(block + st[i], block + ed[i] + 1);
    }
}

inline void Update(register int pos, register int data){
    book[pos] = data;
    for(register int i = st[belong[pos]]; i <= ed[belong[pos]]; i++)
        block[i] = book[i];
    sort(block + st[belong[pos]], block + ed[belong[pos]] + 1);
}

inline int Query(register int l, register int r, register int val){
    register int ans = 0;

    if(belong[l] == belong[r]){
        for(register int i = l; i <= r; i++)
            if(book[i] == val) ans++;
    }
    else{
        for(register int i = l; i <= ed[belong[l]]; i++)
            if(book[i] == val) ans++;
        for(register int i = st[belong[r]]; i <= r; i++)
            if(book[i] == val) ans++;
        for(register int i = belong[l] + 1; i <= belong[r] - 1; i++){
            register int pos_l = lower_bound(block + st[i], block + ed[i] + 1, val) - block;
            register int pos_r = lower_bound(block + st[i], block + ed[i] + 1, val + 1) - block;

            ans += pos_r - pos_l;
        }
    }

    return ans;
}

inline int read(){
    register int x = 0, f = 1;
    register char c = getchar();

    while(c < '0' || c > '9'){
        if(c == '-') f = -1;
        c = getchar();
    }
    while(c >= '0' && c <= '9'){
        x = (x << 1) + (x << 3) + (c ^ 48);
        c = getchar();
    }

    return x * f;
}

inline void write(register int x){
    if(x < 0){
        putchar('-');
        x = -x;
    }
    if(!x) putchar('0');

    register int stk[15], top = 0;
    while(x){
        stk[++top] = x % 10;
        x /= 10;
    }
    while(top) putchar(stk[top--] + 48);
}

int main(){
    n = read(), m = read();
    for(register int i = 1; i <= n; i++)
        book[i] = read();
    
    init(n);

    for(register int i = 1; i <= m; i++){
        register char opt[5];
        scanf("%s", opt + 1);

        if(opt[1] == 'C'){
            register int pos, val;
            pos = read(), val = read();
            Update(pos, val);
        }
        else{
            register int l, r, val;
            l = read(), r = read(), val = read();
            write(Query(l, r, val)), putchar('\n');
        }
    }

    return 0;
}

莫队

只写朴素莫队带修莫队先咕着

概述

莫队,是莫涛归纳总结的一种解决区间查询等问题的离线算法,基于分块思想,复杂度为 \(O(n \sqrt{n})\)

应用

对于序列上的区间询问问题(序列长度为n,询问为m),如果可以在O(1) 内从 [l, r] 的答案扩展到 [l-1, r]、[l+1, r]、[l, r-1]、[l, r+1](即与 [l, r] 相邻的区间)的答案,可以考虑使用莫队算法来解决问题。

实现

离线后排序,顺序处理每个询问,暴力从上一个区间的答案转移到下一个区间答案(一步一步移动即可)。

奇偶化排序

这属于莫队玄学优化的一种。

主要思想是对于属于奇数块的询问,按右端点 \(r\) 从小到大排序;对于属于偶数块的询问,按右端点 \(r\) 从大到小排序。

这样 \(r\) 指针在处理完这个奇数块的问题后,将在返回的途中处理偶数块的问题,再向 \(n\) 移动处理下一个奇数块的问题,优化了 \(r\) 指针的移动次数。

写成这样:

bool cmp(const Question &a, const Question &b){
    if(belong[a.l] != belong[b.l]) return belong[a.l] < belong[b.l];
    if(belong[a.l] & 1) return a.r < b.r;
    return a.r > b.r;
}

题目

P1494 [国家集训队] 小 Z 的袜子

莫队板子。

剩下的难点就是求概率了。

先看一个类似的简化问题:给定一个序列,以及多组询问,针对每组询问,回答该区间有多少方案选出两个值相同的元素(不能调换左右)。

假设有 \(sum\) 个相同的元素,方案数是 \(sum \times (sum - 1) / 2\)

回到这个问题,选到颜色相同的袜子的概率就是选出相同袜子的方案数除以所有的方案数。

最后记得除以分子和分母的 \(gcd\)

Code

点击查看代码
//朴素莫队  
#include<cmath>
#include<cstdio>
#include<algorithm>

using namespace std;

const int MAX=50010;
int n,m,siz;
long long ans;
int color[MAX],cnt[MAX];
long long out1[MAX],out2[MAX];

struct Question{
	int l,r,id;
}q[MAX];

inline int read(){
	int x=0,f=1;
	char c=getchar();
	
	while(c<'0' || c>'9'){
		if(c=='-') f=-1;
		c=getchar();
	}
	while(c>='0' && c<='9'){
		x=(x<<3)+(x<<1)+(c^48);
		c=getchar();
	}
	
	return x*f;
}

long long gcd(long long a,long long b){
	return !b ? a : gcd(b,a%b); 
}

inline bool cmp(const Question &a,const Question &b){
	if(a.l/siz!=b.l/siz) return a.l<b.l;
	else return a.r<b.r;
}

void add(int k){
	ans+=cnt[k];
	cnt[k]++;
}

void del(int k){
	cnt[k]--;
	ans-=cnt[k];
}

void modify(int l,int r){
	for(register int i=1;i<=m;i++){
		if(q[i].l==q[i].r){
			out1[q[i].id]=0,out2[q[i].id]=1;
			continue;
		}
		while(l>q[i].l) add(color[--l]);
		while(l<q[i].l) del(color[l++]);
		while(r<q[i].r) add(color[++r]);
		while(r>q[i].r) del(color[r--]);
		out1[q[i].id]=ans;
		out2[q[i].id]=(long long)(r-l+1)*(r-l)/2;
		if(out1[q[i].id]==0){
			out2[q[i].id]=1;
			continue;	
		}
		long long mod=gcd(out1[q[i].id],out2[q[i].id]);
		out1[q[i].id]/=mod;
		out2[q[i].id]/=mod;
	}
}

int main(){
	n=read(),m=read();
	siz=sqrt(n);
	for(register int i=1;i<=n;i++)
		color[i]=read();
	for(register int i=1;i<=m;i++){
		q[i].l=read(),q[i].r=read();
		q[i].id=i;
	}
	
	sort(q+1,q+1+m,cmp);
	modify(1,0);
	
	for(register int i=1;i<=m;i++)
		printf("%lld/%lld\n",out1[i],out2[i]);
		
	return 0;
}

P2709 小B的询问

同样是模板,只要稍微改一改 \(add\)\(del\) 函数,维护 \(c_{i}^{2}\),就可以了。

Code

点击查看代码
//朴素莫队  
#include<cmath>
#include<cstdio>
#include<algorithm>

using namespace std;

const int MAX=50010;
int n,m,k,siz;
long long ans;
int num[MAX],cnt[MAX];
long long out[MAX];

struct Question{
	int l,r,id;
}q[MAX];

inline int read(){
	int x=0,f=1;
	char c=getchar();
	
	while(c<'0' || c>'9'){
		if(c=='-') f=-1;
		c=getchar();
	}
	while(c>='0' && c<='9'){
		x=(x<<3)+(x<<1)+(c^48);
		c=getchar();
	}
	
	return x*f;
}

inline bool cmp(const Question &a,const Question &b){
	if(a.l/siz!=b.l/siz) return a.l<b.l;
	else return a.r<b.r;
}

void add(int pos){
	ans-=cnt[num[pos]]*cnt[num[pos]];
	cnt[num[pos]]++;
	ans+=cnt[num[pos]]*cnt[num[pos]];
}

void del(int pos){
	ans-=cnt[num[pos]]*cnt[num[pos]];
	cnt[num[pos]]--;
	ans+=cnt[num[pos]]*cnt[num[pos]];
}

void modify(int l,int r){
	for(register int i=1;i<=m;i++){
		while(l>q[i].l) add(--l);
		while(l<q[i].l) del(l++);
		while(r<q[i].r) add(++r);
		while(r>q[i].r) del(r--);
		out[q[i].id]=ans;
	}
}

int main(){
	n=read(),m=read(),k=read();
	siz=sqrt(n);
	for(register int i=1;i<=n;i++)
		num[i]=read();
	for(register int i=1;i<=m;i++){
		q[i].l=read(),q[i].r=read();
		q[i].id=i;
	}
	
	sort(q+1,q+1+m,cmp);
	modify(1,0);
	
	for(register int i=1;i<=m;i++)
		printf("%lld\n",out[i]);
		
	return 0;
}

P3901 数列找不同

更裸的莫队板子,不讲了。

Code

点击查看代码
#include<cmath>
#include<cstdio>
#include<algorithm>

using namespace std;

const int MAX = 1e5 + 10;
int n, m, siz;
long long ans;
int num[MAX], cnt[MAX];
bool out[MAX];

struct Question{
	int l, r, id;
}q[MAX];

inline int read(){
	int x = 0, f = 1;
	char c = getchar();
	
	while(c < '0' || c > '9'){
		if(c == '-') f = -1;
		c = getchar();
	}
	while(c >= '0' && c <= '9'){
		x = (x << 3) + (x << 1) + (c ^ 48);
		c = getchar();
	}
	
	return x * f;
}

inline bool cmp(const Question &a, const Question &b){
	if(a.l / siz != b.l / siz) return a.l < b.l;
	else return a.r < b.r;
}

void add(int k){
	if(cnt[k] == 0) ans++;
	cnt[k]++;
}

void del(int k){
	if(cnt[k] == 1) ans--;
	cnt[k]--;
}

void modify(int l, int r){
	for(register int i = 1; i <= m; i++){
		while(l < q[i].l) del(num[l++]);
		while(r < q[i].r) add(num[++r]);
		while(l > q[i].l) add(num[--l]);
		while(r > q[i].r) del(num[r--]);
		if(ans == q[i].r - q[i].l + 1)
			out[q[i].id] = true;
	}
}

int main(){
	n = read(), m = read();
	siz = sqrt(n);
	for(register int i = 1; i <= n; i++)
		num[i] = read();
	for(register int i = 1; i <= m; i++){
		q[i].l = read(), q[i].r = read();
		q[i].id = i;
	}
	
	sort(q + 1, q + 1 + m, cmp);
	modify(1 ,0);
	
	for(register int i = 1; i <= m; i++)
		out[i] ? puts("Yes") : puts("No");
		
	return 0;
}

P7764 [COCI2016-2017#5] Poklon

同样很裸,对 \(add\)\(del\) 函数稍加改动即可。

Code

点击查看代码
#include<cmath>
#include<cstdio>
#include<algorithm>

using namespace std;

const int MAXN = 5e5 + 10;
int n, m, siz, len, ans;
int num[MAXN], belong[MAXN], cnt[MAXN], val[MAXN], out[MAXN];

struct Question{
	int l, r;
	int id;
}q[MAXN];

inline int read(){
	int x = 0, f = 1;
	char c = getchar();
	
	while(c < '0' || c > '9'){
		if(c == '-') f = -1;
		c = getchar();
	}
	while(c >= '0' && c <= '9'){
		x = (x << 1) + (x << 3) + (c ^ 48);
		c = getchar();
	}
	
	return x * f;
}

bool cmp(const Question &a, const Question &b){
	if(belong[a.l] != belong[b.l]) return belong[a.l] < belong[b.l]; //不在同一块 
	if(belong[a.l] & 1) return a.r > b.r;
	return a.r < b.r; 
}

void Add(int val){
	cnt[val]++;
	if(cnt[val] == 2) ans++;
	if(cnt[val] == 3) ans--;
}

void Del(int val){
	cnt[val]--;
	if(cnt[val] == 2) ans++;
	if(cnt[val] == 1) ans--;
}

void Modify(int l, int r){
	for(register int i = 1; i <= m; i++){
		while(l > q[i].l) Add(num[--l]);
		while(l < q[i].l) Del(num[l++]);
		while(r < q[i].r) Add(num[++r]);
		while(r > q[i].r) Del(num[r--]);
		out[q[i].id] = ans; 
	}
}

int main(){
	n = read(), m = read();
	siz = sqrt(n);
	for(register int i = 1; i <= n; i++){
		num[i] = read();
		val[i] = num[i];
		belong[i] = (i - 1) / siz + 1;
	}
	for(register int i = 1; i <= m; i++){
		q[i].l = read(), q[i].r = read();
		q[i].id = i;
	}
	sort(q + 1, q + 1 + m, cmp);
	
	sort(val + 1, val + 1 + n);
	len = unique(val + 1, val + 1 + n) - val - 1;
	for(register int i = 1; i <= n; i++){
		int pos = lower_bound(val + 1, val + 1 +n, num[i]) - val;
		num[i] = pos;
	}
	
	Modify(1, 0);
	
	for(register int i = 1; i <= m; i++)
		printf("%d\n", out[i]);
	
	return 0;
}

P1903 [国家集训队] 数颜色 / 维护队列

带修莫队,具体先咕掉,以后单开一篇补。

Code

点击查看代码
//带修改莫队 
#include<cmath>
#include<cstdio>
#include<iomanip>
#include<algorithm>

using namespace std;

const int MAXN=140010,MAXM=1e6+10;
int n,m,siz,ans,cnt_question,cnt_change;
int color[MAXN],cnt[MAXM],out[MAXN];

struct Question{
	int l,r,id,t;//t多记录一维上次修改的时间戳 
}q[MAXN];

struct Change{
	int pos,val;
}c[MAXN];

inline int read(){
	int x=0,f=1;
	char c=getchar();
	
	while(c<'0' || c>'9'){
		if(c=='-') f=-1;
		c=getchar();
	}
	while(c>='0' && c<='9'){
		x=(x<<1)+(x<<3)+(c^48);
		c=getchar();
	}
	
	return x*f;
}

inline bool cmp(const Question &a,const Question &b){
	if(a.l/siz!=b.l/siz) return a.l<b.l;
	else{
		if(a.r/siz!=b.r/siz) return a.r<b.r;
		else return a.t<b.t;
	}
}

inline void add(int k){
	if(cnt[k]==0) ans++;
	cnt[k]++;
}

inline void del(int k){
	cnt[k]--;
	if(cnt[k]==0) ans--;
}

void update(int now,int t){
	if(c[now].pos>=q[t].l && c[now].pos<=q[t].r){
		if(--cnt[color[c[now].pos]]==0) ans--;
		if(++cnt[c[now].val]==1) ans++;
	}
	swap(c[now].val,color[c[now].pos]);
}

void modify(int l,int r,int now){
	for(register int i=1;i<=cnt_question;i++){
		while(l<q[i].l) del(color[l++]);
		while(l>q[i].l) add(color[--l]);
		while(r<q[i].r) add(color[++r]);
		while(r>q[i].r) del(color[r--]);
		while(now<q[i].t) update(++now,i);
		while(now>q[i].t) update(now--,i);
		out[q[i].id]=ans;
	}
} 

int main(){
	n=read(),m=read();
	siz=pow(n,2.0/3.0);//带修莫队分块要分成n的三分之一次方块,每块大小为n的三分之二次方 
	for(register int i=1;i<=n;i++)
		color[i]=read();
	for(register int i=1;i<=m;i++){
		char k[2];
		scanf("%s",k);
		if(k[0]=='Q'){
			q[++cnt_question].l=read();
			q[cnt_question].r=read();
			q[cnt_question].t=cnt_change;//记录上次修改的时间戳  
			q[cnt_question].id=cnt_question;
		}
		else if(k[0]=='R'){
			c[++cnt_change].pos=read();
			c[cnt_change].val=read();
		}
	}
	
	sort(q+1,q+1+cnt_question,cmp);
	modify(1,0,0);
	
	for(register int i=1;i<=cnt_question;i++)
		printf("%d\n",out[i]);
	
	return 0;
}

SP3267 DQUERY - D-query

还有个这,同样是很裸的莫队。

Code

点击查看代码
//Waiting 
#include<cmath>
#include<cstdio>
#include<algorithm>

using namespace std;

const int MAXN = 30010, MAXQ = 200010, MAXA = 1e6 + 10;
int n, m, siz, ans;
int belong[MAXN], num[MAXN], cnt[MAXA], out[MAXQ];

struct Question{
    int l, r, id;
}q[MAXQ];

inline int read(){
	int x = 0, f = 1;
	char c = getchar();
	
	while(c < '0' || c > '9'){
		if(c == '-') f = -1;
		c = getchar();
	}
	while(c >= '0' && c <= '9'){
		x = (x << 3) + (x << 1) + (c ^ 48);
		c = getchar();
	}
	
	return x * f;
}

bool cmp(const Question &a, const Question &b){
    if(belong[a.l] != belong[b.l]) return belong[a.l] < belong[b.l];
    if(belong[a.l] & 1) return a.r > b.r;
    return a.r < b.r;
}

void Add(int val){
    if(cnt[val] == 0)
        ans++;
    cnt[val]++;
}

void Del(int val){
    if(cnt[val] == 1)
        ans--;
    cnt[val]--;
}

void Modify(int l, int r){
    for(register int i = 1; i <= m; i++){
        while(l > q[i].l) Add(num[--l]);
		while(l < q[i].l) Del(num[l++]);
		while(r < q[i].r) Add(num[++r]);
		while(r > q[i].r) Del(num[r--]);
		out[q[i].id] = ans; 
    }
}

int main(){
    n = read();
    siz = sqrt(n);
    for(register int i = 1; i <= n; i++){
        num[i] = read();
        belong[i] = (i - 1) / siz + 1;
    }
        
    m = read();
    for(register int i = 1; i <= m; i++){
        int l, r;
        l = read(), r = read();
        q[i].l = l;
        q[i].r = r;
        q[i].id = i;
    }
    sort(q + 1, q + 1 + m, cmp);

    Modify(1, 0);

    for(register int i = 1; i <= m; i++)
        printf("%d\n", out[i]);

    return 0;
}
posted @ 2022-08-05 21:41  TSTYFST  阅读(31)  评论(1编辑  收藏  举报