• 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;
    }
    
    转载需注明出处。
  • 相关阅读:
    扩展欧几里得(exgcd)与同余详解
    卡常模板
    文艺平衡树(区间翻转)
    Motto
    PKUWC2019划水记
    【模板】Splay(洛谷P3391)
    【PKUSC2018】最大前缀和
    【PKUWC2018】随机算法
    【PKUWC2018】Slay the Spire
    【PKUWC2018】Minimax
  • 原文地址:https://www.cnblogs.com/jz929/p/11817575.html
Copyright © 2020-2023  润新知