墨墨购买了一套N支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问。
墨墨会像你发布如下指令:
1、 Q L R代表询问你从第L支画笔到第R支画笔中共有几种不同颜色的画笔。
2、 R P Col 把第P支画笔替换为颜色Col。
为了满足墨墨的要求,你知道你需要干什么了吗?
标签:左右 方便 初始 def can 中间 暴力枚举 你知道 color
6 5
1 2 3 4 5 5
Q 1 4
Q 2 6
R 1 2
Q 1 4
Q 2 6
4
4
3
4
思路:
对于这一题,可以用分块,也可以用带修改的莫队;
我先讲下分块写法
可以用一个pre数字存每一个颜色上一次出现的位置;
如果这个pre位置是小于询问区间的左端点的,那么代表这个颜色是在这个询问区间内第一次出现的;
这样就可以查找这个区间内有多少个不同的颜色;
然后,我们可以定义一个last数组,存这个颜色的最后出现位置;
同时last数组可以方便我们更好的记录pre;
然后是修改操作;
可以重新再把每一个颜色的pre重新构建;
如
inline void findout(ll x,ll y) { for(ll i=1;i<=n;i++) last[color[i]]=0; color[x]=y; for(ll i=1;i<=n;i++) { ll num=pre[i]; pre[i]=last[color[i]]; last[color[i]]=i; if(pre[i]!=num) dowork(i); } }
也可以找到与被修改的颜色相关的点,然后用if修改关于这个颜色的pre,last数组;
这样修改将比上面的代码更快;
代码,最后会给出;
最后,总结下思路,
分块,排序后二分找整块,暴力枚举小棱块,查找pre位置小于询问区间的左端的那个颜色
附上熬夜修改的代码:
#include<bits/stdc++.h> using namespace std; typedef long long ll; inline ll read() { ll a=0,f=1; char c=getchar(); while (c<‘0‘||c>‘9‘) {if (c==‘-‘) f=-1; c=getchar();} while (c>=‘0‘&&c<=‘9‘) {a=a*10+c-‘0‘; c=getchar();} return a*f; } ll n,m,blo; ll a[1000001],color[1000001],sv[1000001];//a数组是每个位置所处的分块, //sv是按每个分块排序后的pre值 ll pre[1000001],last[1000001]; inline ll L(ll x)//懒人专用 //求每个分块左右的端点 { return (x-1)*blo+1; } inline ll R(ll x) { return x*blo; } inline void dowork(ll x) { for(ll i=L(a[x]);i<=R(a[x]);i++) sv[i]=pre[i]; sort(sv+L(a[x]),sv+R(a[x])+1);//pre修改后,重新排序构建,便于二分查找 } inline void findout(ll x,ll y)//x表示被修改的位置,y是要变成的颜色 { ll cox=color[x];//记录这个颜色 color[x]=y; //修改 ll now=last[cox];//记录这个要被修改的颜色的最后一个点 if(now==x)//如果最后一个点要被修改 last[cox]=pre[x];//那么这个颜色的最后一个点,将是被修改的颜色的pre else if(now>x)//若 x.........now 不会出现x<now的情况,因为now=last[cox]; { while(pre[now]!=x)//不断往前找,使得pre[now]==x now=pre[now]; pre[now]=pre[x];// pre[x]......x......now 因为x会被修改,所以 pre[now]=pre[x] dowork(now);//因为pre被修改了,所以now所在的块重新排序 } now=last[y];//记下y颜色的最后一个位置 if(now<x)//因为 y!=x 所以 不考虑now==x的情况 { last[y]=x;// now......x y颜色的最后位置将会变成x pre[x]=now; dowork(x);//重新排序 } else if(now>x)//x......now { while(pre[now]>x)//不断找到,使得pre[now]<x now=pre[now]; pre[x]=pre[now];//pre[now]....x.....now pre[now]=x; dowork(x); dowork(now); } } //inline void findout(ll x,ll y) //重新构建方法,比上面的更慢 //{ // for(ll i=1;i<=n;i++) // last[color[i]]=0; //所以颜色的最后一个位置清零 // color[x]=y; // for(ll i=1;i<=n;i++)//重新枚举构建 // { // ll num=pre[i]; // pre[i]=last[color[i]]; // last[color[i]]=i; // if(pre[i]!=num)//如果重新构建之前 与构建之后的pre值改变了 // dowork(i);//就整块分块内 重新排序 // } //} inline ll reallyans(ll x,ll y) { ll sum=0; if(a[x]==a[y])//如果询问区间处在一个分块,就暴力 { for(ll i=x;i<=y;i++) if(pre[i]<x) //查找pre位置小于询问区间的左端的那个颜色 sum++; return sum; } else { for(ll i=a[x]+1;i<=a[y]-1;i++) { ll l=L(i),r=R(i),ss=0,mid; while(l<=r) { mid=(l+r)>>1; if(sv[mid]<x) l=mid+1, ss=mid-L(i)+1;//如果中间的那个sv的位置都小于x //因为是排好序的,所以左边的位置都小于x else r=mid-1; } sum+=ss; }//二分找位置小于询问区间的左端的那个颜色 for(ll i=x;i<=R(a[x]);i++)//暴模边角 if(pre[i]<x) sum++; for(ll i=L(a[y]);i<=y;i++) if(pre[i]<x) sum++; return sum; } } int main() { n=read();m=read(); blo=sqrt(n); for(ll i=1;i<=n;i++) { color[i]=read(); a[i]=(i-1)/blo+1; pre[i]=last[color[i]]; last[color[i]]=i; }//记录pre和last的值 for(ll i=1;i<=n;i++) sv[i]=pre[i]; for(ll i=1;i<=a[n];i++) sort(sv+L(i),sv+R(i)+1);//每个分块内都排好序 char cc[4]; ll ans; for(ll i=1;i<=m;i++) { scanf("%s",cc); ll x=read(),y=read(); if(cc[0]==‘R‘) findout(x,y); else if(cc[0]==‘Q‘) { ans=reallyans(x,y); printf("%lld\n",ans); } } return 0;//别忘了return 0 ^_^ }
标签:左右 方便 初始 def can 中间 暴力枚举 你知道 color
原文地址:https://www.cnblogs.com/wzx-RS-STHN/p/12940917.html