bzoj 2120. 数颜色(暴力,分块和莫队)

这题一看数据范围,发现可以暴力,想着想打个暴力对了再说,结果T飞了~
暴力程序:

#include<cstdio>
#define N 10010
#define M 1000010
using namespace std;
int n,m,a[N],hav[M],s,x,y,tot=0;
char ch;

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

int main()
{
	freopen("2120.in","r",stdin);
	freopen("2120.out","w",stdout);
	n=read(),m=read();
	for (int i=1;i<=n;i++) a[i]=read();
	for (int i=1;i<=m;i++)
	{
		ch=getchar();
		while (ch!='Q' && ch!='R') ch=getchar();
		if (ch=='Q')
		{
			x=read(),y=read(),s=0;
			for (int j=x;j<=y;j++)
				if (hav[a[j]]!=i) s++,hav[a[j]]=i;
			printf("%d
",s);
		}
		else x=read(),y=read(),a[x]=y;
	}
	return 0;
}

在这里插入图片描述

好像都是这玩意惹的祸!!!

只好打分快或者待修莫队了。
我先打了个分块。
思路是从hzwer大佬那里来的。
我们设b[]表示上一个为a[i]颜色的点。
然后将b[]分成根号n块并将每块按照b[]从小到大排序,存入数组pre[]。
如果是修改的话:
我们就重新做一遍b[]和pre[]。
如果是查找答案的话:
我们对于两边凸出来的暴力用b[]算,
而中间的就用pre[]二分来算。
最后将全部答案加起来即可。
上标:(好像luogu的数据卡不过(n<=50,000))

#include<cstdio>
#include<algorithm>
#include<cmath>
#define N 10010
#define M 1000010
using namespace std;
int n,m,a[N],las[M],st,x,y,tot=0;
int bl[N],le[N],ri[N],bf[N],pre[N];
char ch;

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

void reset(int x)
{
	for (int i=le[x];i<=ri[x];i++) pre[i]=bf[i];
	sort(pre+le[x],pre+ri[x]+1);
}

int find(int x,int val)
{
	int l=le[x],r=ri[x],mid;
	while (l<=r)
	{
		mid=l+r>>1;
		if (pre[mid]<val) l=mid+1;
		else r=mid-1;
	}
	return l-le[x];
}

int ask(int l,int r)
{
	int s=0;
	for (int i=l;i<=min(ri[bl[l]],r);i++)
		if (bf[i]<l) s++;
	if (bl[l]<bl[r])
	{
		for (int i=le[bl[r]];i<=r;i++)
			if (bf[i]<l) s++;
	}
	for (int i=bl[l]+1;i<bl[r];i++)
		s+=find(i,l);
	return s;
}

void change(int x,int val)
{
	for (int i=1;i<=n;i++) las[a[i]]=0;
	a[x]=val;
	for (int i=1,t;i<=n;i++)
	{
		t=bf[i],bf[i]=las[a[i]];
		if (bf[i]!=t) reset(bl[i]);
		las[a[i]]=i;
	}
}

int main()
{
	freopen("2120.in","r",stdin);
	freopen("2120.out","w",stdout);
	n=read(),m=read(),st=sqrt(n);
	for (int i=1;i<=n;i++)
	{
		a[i]=read(),bl[i]=(i-1)/st+1;
		bf[i]=las[a[i]],las[a[i]]=i;
		if (!le[bl[i]]) le[bl[i]]=i;
		ri[bl[i]]=i;
	}
	for (int i=1;i<=bl[n];i++) reset(i);
	for (int i=1;i<=m;i++)
	{
		ch=getchar();
		while (ch!='Q' && ch!='R') ch=getchar();
		x=read(),y=read();
		if (ch=='Q') printf("%d
",ask(x,y));
		else change(x,y);
	}
	return 0;
}

现在再来想想怎么用带修莫队吧。
带修莫队加了个修改操作。

我们要将询问和修改分开来排序

询问:便按照l所在的块为第一关键字,r所在的块为第二关键字,时间为第三关键字来从小到大排序。
修改:便直接按照时间来从小到大排序。
在枚举询问的时候,就将左指针,右指针和修改位置移动一下就可以啦。
上标:

#include<cstdio>
#include<algorithm>
#include<cmath>
#define N 10010
#define M 1000010
using namespace std;
struct node{int l,r,fr,las;}Q[N],R[N];
int n,m,a[N],b[N],bl[N],hav[M],x,y,tot=0,cnt=0,st;
int l,r,ti,s=0,Ans[N];
char ch,check;

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

int cmp(node x,node y) {return bl[x.l]==bl[y.l] ? (bl[x.r]==bl[y.r] ? x.fr<y.fr : bl[x.r]<bl[y.r]) : bl[x.l]<bl[y.l];}

int cmp1(node x,node y) {return x.fr<y.fr;}

int main()
{
	freopen("2120.in","r",stdin);
	freopen("2120.out","w",stdout);
	n=read(),m=read(),st=sqrt(n);
	for (int i=1;i<=n;i++)
		a[i]=read(),bl[i]=(i-1)/st+1//是(i-1)/st+1,而不是(i-1)*st+1
	for (int i=1;i<=m;i++)
	{
		ch=getchar();
		while (ch!='Q' && ch!='R') ch=getchar();
		x=read(),y=read();
		if (ch=='Q') Q[++tot]=(node){x,y,i};
		else R[++cnt]=(node){x,y,i},Ans[i]=-1;
	}
	sort(Q+1,Q+tot+1,cmp);
//	sort(R+1,R+cnt+1,cmp1);//这条其实不需要的
	l=1,r=0,ti=0;
	for (int i=1;i<=tot;i++)
	{
		while (ti<cnt && R[ti+1].fr<Q[i].fr)
		{
			ti++;
			check=R[ti].l>=l && R[ti].l<=r;
			R[ti].las=a[R[ti].l];
			if (check) s-=! (--hav[a[R[ti].l]]);
			a[R[ti].l]=R[ti].r;
			if (check) s+=! (hav[a[R[ti].l]]++);
		}
		while (R[ti].fr>Q[i].fr)
		{
			check=R[ti].l>=l && R[ti].l<=r;
			if (check) s-=! (--hav[a[R[ti].l]]);
			a[R[ti].l]=R[ti].las;
			if (check) s+=! (hav[a[R[ti].l]]++);
			ti--;
		}
		while (l<Q[i].l) s-=! (--hav[a[l++]]);
		while (l>Q[i].l) s+=! (hav[a[--l]]++);
		while (r>Q[i].r) s-=! (--hav[a[r--]]);
		while (r<Q[i].r) s+=! (hav[a[++r]]++);
		Ans[Q[i].fr]=s;
	}
	for (int i=1;i<=m;i++)
		if (Ans[i]!=-1) printf("%d
",Ans[i]);
	return 0;
}

因为代码上面写着的问题,我只好重打了一遍,改了点东西。
我直接在询问数组中存了当前修改的编号,这样子修改数组就不用排序了。

等等。。。

之前的修改好像本来就不需要排序!!!

好吧,改了没个卵用。。。

上标:

#include<cstdio>
#include<algorithm>
#include<cmath>
#define N 10010
#define M 1000010
using namespace std;
struct ask{int l,r,time,fr;}Q[N];
struct change{int l,r,las;}R[N];
int n,m,a[N],bl[N],hav[M],tot=0,cnt=0,st;
int l=1,r=0,ti=0,s=0,Ans[N];
char ch;

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

int cmp(ask x,ask y) {return bl[x.l]==bl[y.l] ? (bl[x.r]==bl[y.r] ? x.time<y.time : bl[x.r]<bl[y.r]) : bl[x.l]<bl[y.l];}

int main()
{
	freopen("2120.in","r",stdin);
	freopen("2120.out","w",stdout);
	n=read(),m=read(),st=pow(n,0.66666);
	for (int i=1;i<=n;i++)
		a[i]=read(),bl[i]=(i-1)/st+1;
	for (int i=1,x,y;i<=m;i++)
	{
		scanf(" %c",&ch),x=read(),y=read();
		if (ch=='Q') Q[++tot]=(ask){x,y,cnt,tot};
		else R[++cnt]=(change){x,y};
	}
	sort(Q+1,Q+tot+1,cmp);
	for (int i=1;i<=tot;i++)
	{
		while (ti<Q[i].time)
		{
			ti++;
			if (l<=R[ti].l && R[ti].l<=r)
			{
				if (!--hav[a[R[ti].l]]) s--;
				if (!hav[R[ti].r]++) s++;
			}
			R[ti].las=a[R[ti].l];
			a[R[ti].l]=R[ti].r;
		}
		while (ti>Q[i].time)
		{
			if(l<=R[ti].l && R[ti].l<=r)
			{
				if (!--hav[a[R[ti].l]]) s--;
				if (!hav[R[ti].las]++) s++; 
			}
			a[R[ti].l]=R[ti].las;
			ti--;
		}
		while (l<Q[i].l) if (!--hav[a[l++]]) s--;
		while (l>Q[i].l) if (!hav[a[--l]]++) s++;
		while (r>Q[i].r) if (!--hav[a[r--]]) s--;
		while (r<Q[i].r) if (!hav[a[++r]]++) s++;
		Ans[Q[i].fr]=s;
	}
	for (int i=1;i<=tot;i++)
		printf("%d
",Ans[i]);
	return 0;
}
转载需注明出处。
原文地址:https://www.cnblogs.com/jz929/p/11817575.html