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\n",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\n",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\n",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\n",Ans[i]);
return 0;
}
转载需注明出处。