数颜色
Cici有一些彩盒,Cici喜欢把它们排成队列,从左到右编号为1到N。为了整个队列鲜艳美观,小朋友想知道某一段连续彩盒中,不同颜色的彩盒有多少。当然,Cici有时候会替换队列中某个彩盒的颜色。
Input
第一行包含两个整数N和M。
第二行N个整数,表示初始队列中彩盒的颜色。
接下来M行
每行的形式为“Q L R”或“R x c”,
“Q L R”表示Cici想知道从队列第L个彩盒到第R个彩盒中,一共有多少不同颜色的彩盒,
“R x c”表示Cici把x位置上的彩盒换成了c颜色。
有1 ≤ N ≤ 10000, 1 ≤ M ≤ 10000,
小朋友Cici不会修改超过1000次,所有颜色均用1到10^6的整数表示。
Output
对于每个Q操作,输出一行表示询问结果。
Sample Input
2 3
1 2
Q 1 2
R 1 2
Q 1 2
Sample Output
2
1
Sol:
此题主要是维护的问题,当改变一个位置的color,则影响两个color的前后关系。
用pre[i]表示第i个元素的前一个相同元素的位置,
问题变成在区间[l,r]里找大于l的数的个数,
(具体什么意思去其他地方看)默认值为最大值。
用ppp[i]表示颜色i最后出现的位置(针对这道题颜色最多1e6种,建一个1e6的数组即可)
color[i]表示第i个位置是什么颜色
========================================================================
本题要统计某个区间,出现过多少种不同的color.
于是
我们可以维护下某个区间[x,y]上每个点,它的color,上一次出现的位置pre[i]
当我们走到一个点i,它的pre[i]<x,则在当前出现了一次,上一次出现位置又小于x,则说明是第一次被统计到。
根据上面这个思路,我们可以分块处理。
离散块暴力来找,整块操作时,可以二分来找当前区间中有多少个数字是小于x的。
修改操作
还要记下某个color,设为a最后出现的位置ppp[a],形成一个链表pre[a]..ppp[a]。
当将x位置上的color,换成另一种color时
先对删除color的点进行讨论
1:为最后一个点
2:不为最后一个点
再加换上去的color进行讨论,设其最后的位置为c
形为pre[c]............c
分类如下
1:在c的右边
2:在pre[c]与c之间
3:在pre[c]的左边形如x.......pre[c]
此时就要找到一个位置c',形成pre[c'].....x...c'
//https://blog.csdn.net/shisuan1/article/details/81268270 #include<stdio.h> #include<string.h> #include<algorithm> #include<math.h> #define inf 0x3f3f3f3f using namespace std; int a[205][205]; int pre[200005]; int color[200005]; int ppp[1000005]; int n,m,l,r; void reset(int x) { int p=(x-1)/l; //x所在的块 for(int j=1;j<=l;j++) { a[p][j]=pre[p*l+j]; } sort(a[p]+1,a[p]+l+1); } void build() { for(int i=0;i<r;i++) //枚举块的个数 { for(int j=1;j<=l;j++) //枚举块大小 { a[i][j]=pre[i*l+j]; } sort(a[i]+1,a[i]+l+1); } } int search(int x,int y) { int ans=0; int p=(x-1)/l+1; int q=(y-1)/l; if(p>=q) { for(int i=x;i<=y;i++) //枚举位置 if(pre[i]<x) //如果这个位置的color上一次出现在x的左边,则当前出现过一次 //如果是大于x,则说明在[x,y]之间出现过不止一次 ans++; return ans; } for(int i=x;i<=p*l;i++) if(pre[i]<x) ans++; for(int i=q*l+1;i<=y;i++) if(pre[i]<x) ans++; for(int i=p;i<q;i++) //对于中间的块,枚举每一块,看它里面存的数字,有多少是<x的 { ans+=lower_bound(a[i]+1,a[i]+l+1,x)-a[i]-1; //后面有个减1,于是就是找<x的了 } return ans; } void change(int x,int y)//位置x,颜色y. { int p=color[x]; //记下x位置上从前的color color[x]=y; //更新为当前的color int c=ppp[p]; //c为x这个位置上从前color的最后出现的位置 if(c==x) //如果这个位置等于x,即最后一个点没有了 { ppp[p]=pre[c]; //则最后出现的位置变成pre[c] } else //说明被改变color的不是其最后一个位置 { while(pre[c]!=x) //不断去找这个位置 c=pre[c]; pre[c]=pre[x]; //形如pre(x)......x......c,因为x这个位置去掉了,于是pre[c]=pre[x] reset(c); //对c所在的段进行重构 } c=ppp[y]; //c代表color为y的最后出现的位置 if(c<x) //如果这个位置小于x,形如c.....x则x是这个color的最后出现位置 { pre[x]=c; ppp[y]=x; reset(x); } else if(pre[c]<x) //形如pre[c]....x......c { pre[x]=pre[c]; pre[c]=x; reset(x); reset(c); } else //形如x.....pre[c] { while(pre[c]>x) { c=pre[c]; } //确定一个c的位置pre[c]....x....c pre[x]=pre[c]; pre[c]=x; reset(x); reset(c); } } int main() { //freopen("input.txt","r",stdin); memset(ppp,0,sizeof(ppp)); memset(pre,inf,sizeof(pre)); pre[0]=0; scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) { scanf("%d",&color[i]); if(ppp[color[i]]!=0) //ppp[i]表示颜色i最后出现的位置 pre[i]=ppp[color[i]]; else pre[i]=0; //pre[i]表示颜色i上一次出现的位置 ppp[color[i]]=i; } l=floor(sqrt(n)); //块的大小 if(n%l>0) r=n/l+1; //块的个数 else r=n/l; build(); int x,y; char c[10]; for(int i=0;i<m;i++) { scanf("%s%d%d",c,&x,&y); if(c[0]=='Q') printf("%d\n",search(x,y)); else change(x,y); } return 0; }
带修改的莫队算法
https://www.cnblogs.com/RabbitHu/p/MoDuiTutorial.html
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; typedef long long ll; #define space putchar(' ') #define enter putchar('\n') template <class T> void read(T &x){ char c; bool op = 0; while(c = getchar(), c < '0' || c > '9') if(c == '-') op = 1; x = c - '0'; while(c = getchar(), c >= '0' && c <= '9') x = x * 10 + c - '0'; if(op) x = -x; } template <class T> void write(T x){ if(x < 0) x = -x, putchar('-'); if(x >= 10) write(x / 10); putchar('0' + x % 10); } const int N = 10005, M = 1000005, B = 464; int n, m, pl = 1, pr = 0, cur, res, ans[N], a[N], cnt[M]; int idxC, idxQ, tim[N], pos[N], val[N], pre[N]; #define bel(x) (((x) - 1) / B + 1) struct query { int id, tim, l, r; bool operator < (const query &b) const { if(bel(l) != bel(b.l)) return l < b.l; if(bel(r) != bel(b.r)) return r < b.r; return id < b.id; } } q[N]; void change_add(int cur) { if(pos[cur] >= pl && pos[cur] <= pr) { cnt[a[pos[cur]]]--; if(!cnt[a[pos[cur]]]) res--; } pre[cur] = a[pos[cur]];//记下从前的color a[pos[cur]] = val[cur]; //换上新的color if(pos[cur] >= pl && pos[cur] <= pr) { if(!cnt[a[pos[cur]]]) res++; cnt[a[pos[cur]]]++; } } void change_del(int cur) { if(pos[cur] >= pl && pos[cur] <= pr) { cnt[a[pos[cur]]]--; if(!cnt[a[pos[cur]]]) res--; } a[pos[cur]] = pre[cur]; if(pos[cur] >= pl && pos[cur] <= pr) { if(!cnt[a[pos[cur]]]) res++; cnt[a[pos[cur]]]++; } } void change(int now) { while(cur < idxC && tim[cur + 1] <= now) //cur代表目前的修改操作进行到一个了,如果还没到当前询问的时间点 //则有些修改操作还没有做,加进来 change_add(++cur); while(cur && tim[cur] > now) //也有可能是多做了修改操作,于是删除这些修改操作 change_del(cur--); } void add(int p) { if(!cnt[a[p]]) res++; cnt[a[p]]++; } void del(int p) { cnt[a[p]]--; if(!cnt[a[p]]) res--; } bool isQ() { char op[2]; scanf("%s", op); return op[0] == 'Q'; } int main() { read(n), read(m); for(int i = 1; i <= n; i++) read(a[i]); for(int i = 1; i <= m; i++) { if(isQ()) //查询操作 { idxQ++; q[idxQ].id = idxQ; q[idxQ].tim = i; read(q[idxQ].l); read(q[idxQ].r); } else //修改操作 { tim[++idxC] = i; //第idxc个修改操作发生的时间 read(pos[idxC]); //修改的位置 read(val[idxC]); //修改后的color } } sort(q + 1, q + idxQ + 1); for(int i = 1; i <= idxQ; i++) { change(q[i].tim); //以当前这个查询为基准,是否有修改操作没有做,或者多做了 while(pl > q[i].l) add(--pl); while(pr < q[i].r) add(++pr); while(pl < q[i].l) del(pl++); while(pr > q[i].r) del(pr--); ans[q[i].id] = res; } for(int i = 1; i <= idxQ; i++) write(ans[i]), enter; return 0; }