P2464 [SDOI2008]郁闷的小J
题目描述
小J是国家图书馆的一位图书管理员,他的工作是管理一个巨大的书架。虽然他很能吃苦耐劳,但是由于这个书架十分巨大,所以他的工作效率总是很低,以致他面临着被解雇的危险,这也正是他所郁闷的。
具体说来,书架由N个书位组成,编号从1到N。每个书位放着一本书,每本书有一个特定的编码。
小J的工作有两类:
1.图书馆经常购置新书,而书架任意时刻都是满的,所以只得将某位置的书拿掉并换成新购的书。
2.小J需要回答顾客的查询,顾客会询问某一段连续的书位中某一特定编码的书有多少本。
例如,共5个书位,开始时书位上的书编码为1,2,3,4,5
一位顾客询问书位1到书位3中编码为“2”的书共多少本,得到的回答为:1
一位顾客询问书位1到书位3中编码为“1”的书共多少本,得到的回答为:1
此时,图书馆购进一本编码为“1”的书,并将它放到2号书位。
一位顾客询问书位1到书位3中编码为“2”的书共多少本,得到的回答为:0
一位顾客询问书位1到书位3中编码为“1”的书共多少本,得到的回答为:2
……
你的任务是写一个程序来回答每个顾客的询问。
输入输出格式
输入格式:
第一行两个整数N,M,表示一共N个书位,M个操作。
接下来一行共N个整数数A1,A2…AN,Ai表示开始时位置i上的书的编码。
接下来M行,每行表示一次操作,每行开头一个字符
若字符为‘C’,表示图书馆购进新书,后接两个整数A(1<=A<=N),P,表示这本书被放在位置A上,以及这本书的编码为P。
若字符为‘Q’,表示一个顾客的查询,后接三个整数A,B,K(1<=A<=B<=N),表示查询从第A书位到第B书位(包含A和B)中编码为K的书共多少本。
输出格式:
对每一个顾客的查询,输出一个整数,表示顾客所要查询的结果。
输入输出样例
5 5
1 2 3 4 5
Q 1 3 2
Q 1 3 1
C 2 1
Q 1 3 2
Q 1 3 1
1
1
0
2
说明
对于40%的数据,1<=N,M<=5000
对于100%的数据,1<=N,M<=100000
对于100%的数据,所有出现的书的编码为不大于2147483647的正数。
Solution:
本题解法太多,前后用了4种方法去做,由简入繁。
法一:分块+map(736ms)
我们可以将数列划分为$\sqrt n$块,每个块用map维护块内元素出现次数,那么单次修改可以做到$O(\log(\sqrt n))$,单次查询能做到$\sqrt n \log (\sqrt n)$。时间复杂度$O(n\sqrt n \log(\sqrt n))$,极限数据能卡到$2e8$,但是本题数据比较水也能过。
法一代码:
/*Code by 520 -- 10.28*/
#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/hash_policy.hpp>
#define il inline
#define ll long long
#define RE register
#define For(i,a,b) for(RE int (i)=(a);(i)<=(b);(i)++)
#define Bor(i,a,b) for(RE int (i)=(b);(i)>=(a);(i)--)
using namespace std;
using namespace __gnu_pbds;
const int N=;
gp_hash_table<int,int>mp[];
int n,m,a[N],bl[N],ln[N],rn[N],clo,u,v,w;
char opt[]; int gi(){
int a=;char x=getchar();
while(x<''||x>'') x=getchar();
while(x>=''&&x<='') a=(a<<)+(a<<)+(x^),x=getchar();
return a;
} il int query(int x,int y,int z){
int bx=bl[x],by=bl[y],res=;
if(bx==by) {
For(i,x,y) res+=(a[i]==z);
return res;
}
For(i,bx+,by-) res+=mp[i][z];
For(i,x,rn[bx]) res+=(a[i]==z);
For(i,ln[by],y) res+=(a[i]==z);
return res;
} int main(){
n=gi(),m=gi(); clo=sqrt(n);
For(i,,n) {
a[i]=gi(),bl[i]=(i-)/clo+,mp[bl[i]][a[i]]++;
if(!ln[bl[i]]) ln[bl[i]]=i;
rn[bl[i]]=i;
}
For(i,,m){
scanf("%s",opt);
if(opt[]=='Q') u=gi(),v=gi(),w=gi(),printf("%d\n",query(u,v,w));
else {
u=gi(),v=gi();
mp[bl[u]][a[u]]--;
a[u]=v;
mp[bl[u]][a[u]]++;
}
}
return ;
}
法二:分块+离散化(383ms)
我们显然可以用奇技淫巧优化掉法一中的$\log(\sqrt n)$。只需要离线操作,并对数的值域离散,然后用空间换时间,一种方法是把块数调小,另一种是直接用short类型来开桶(反正一个块内的元素次数不会超过$\sqrt n<2^{16}-1$),能卡着空间过。时间复杂度$O(n\sqrt n)$。
法二代码:
/*Code by 520 -- 10.28*/
#include<bits/stdc++.h>
#define il inline
#define ll long long
#define RE register
#define For(i,a,b) for(RE int (i)=(a);(i)<=(b);(i)++)
#define Bor(i,a,b) for(RE int (i)=(b);(i)>=(a);(i)--)
using namespace std;
const int N=;
int n,m,a[N],bl[N],ln[N],rn[N],clo,u,v,w,*q[N<<],cnt;
struct node{
int l,r,x;
}t[N];
short mp[][N<<];
char opt[N][]; int gi(){
int a=;char x=getchar();
while(x<''||x>'') x=getchar();
while(x>=''&&x<='') a=(a<<)+(a<<)+(x^),x=getchar();
return a;
} il bool cmp(int *a,int *b){return *a<*b;} il int query(int x,int y,int z){
int bx=bl[x],by=bl[y],res=;
if(bx==by) {
For(i,x,y) res+=(a[i]==z);
return res;
}
For(i,bx+,by-) res+=mp[i][z];
For(i,x,rn[bx]) res+=(a[i]==z);
For(i,ln[by],y) res+=(a[i]==z);
return res;
} int main(){
n=gi(),m=gi(); clo=sqrt(n);
For(i,,n) {
a[i]=gi(),q[++cnt]=&a[i],bl[i]=(i-)/clo+;
if(!ln[bl[i]]) ln[bl[i]]=i;
rn[bl[i]]=i;
}
For(i,,m){
scanf("%s",opt[i]);
if(opt[i][]=='Q') t[i]=node{gi(),gi(),gi()},q[++cnt]=&t[i].x;
else t[i]=node{gi(),gi(),},q[++cnt]=&t[i].r;
}
sort(q+,q+cnt+,cmp); int lst=-,tot=;
For(i,,cnt) if(*q[i]!=lst) lst=*q[i],*q[i]=++tot; else *q[i]=tot;
For(i,,n) mp[bl[i]][a[i]]++;
For(i,,m){
if(opt[i][]=='Q') printf("%d\n",query(t[i].l,t[i].r,t[i].x));
else {
u=t[i].l,v=t[i].r;
mp[bl[u]][a[u]]--;
a[u]=v;
mp[bl[u]][a[u]]++;
}
}
return ;
}
法三:带修改主席树(1156ms )
本题显然是个带修主席树的板子,只需要离线操作并对值域离散,然后就直接板子咯。时间复杂度$O(n\log^2 n)$。
法三代码:
/*Code by 520 -- 10.28*/
#include<bits/stdc++.h>
#define il inline
#define ll long long
#define RE register
#define For(i,a,b) for(RE int (i)=(a);(i)<=(b);(i)++)
#define Bor(i,a,b) for(RE int (i)=(b);(i)>=(a);(i)--)
using namespace std;
const int N=;
int n,m,a[N],*q[N<<],cnt,tot,rt[N],X[N],Y[N],tx,ty;
struct query{
int l,r,x;
}qus[N];
struct node{
int ls,rs,sz;
}t[N*];
char opt[N][]; int gi(){
int a=;char x=getchar();
while(x<''||x>'') x=getchar();
while(x>=''&&x<='') a=(a<<)+(a<<)+(x^),x=getchar();
return a;
} il bool cmp(int *a,int *b){return *a<*b;} void ins(int l,int r,int k,int x,int lst,int &rt){
if(!rt) rt=++tot; t[rt]=t[lst],t[rt].sz+=x;
if(l==r) return;
int m=l+r>>;
if(k<=m) ins(l,m,k,x,t[lst].ls,t[rt].ls);
else ins(m+,r,k,x,t[lst].rs,t[rt].rs);
} il void update(int i,int v){
int k=a[i];
while(i<=n) ins(,cnt,k,v,rt[i],rt[i]),i+=i&-i;
} il int calc(int x){
int l=,r=cnt,k=qus[x].x,res=;
tx=ty=;
for(RE int i=qus[x].l-;i;i-=i&-i) X[++tx]=rt[i];
for(RE int i=qus[x].r;i;i-=i&-i) Y[++ty]=rt[i];
while(){
int mid=l+r>>;
if(l==r) break;
if(mid>=k) {
r=mid;
For(i,,tx) X[i]=t[X[i]].ls;
For(i,,ty) Y[i]=t[Y[i]].ls;
}
else {
l=mid+;
For(i,,tx) X[i]=t[X[i]].rs;
For(i,,ty) Y[i]=t[Y[i]].rs;
}
}
For(i,,ty) res+=t[Y[i]].sz;
For(i,,tx) res-=t[X[i]].sz;
return res;
} int main(){
n=gi(),m=gi();
For(i,,n) a[i]=gi(),q[++cnt]=&a[i];
For(i,,m){
scanf("%s",opt[i]);
if(opt[i][]=='Q') qus[i]=query{gi(),gi(),gi()},q[++cnt]=&qus[i].x;
else qus[i]=query{gi(),gi(),},q[++cnt]=&qus[i].r;
}
sort(q+,q+cnt+,cmp); int lst=-;
For(i,,cnt) if(*q[i]!=lst) lst=*q[i],*q[i]=++tot; else *q[i]=tot;
cnt=tot;
memset(&t[tot=],,sizeof(t[]));
For(i,,n) update(i,);
For(i,,m){
if(opt[i][]=='Q') printf("%d\n",calc(i));
else {
int u=qus[i].l,v=qus[i].r;
update(u,-),a[u]=v,update(u,);
}
}
return ;
}
法四:平衡树(432ms)
我们离线操作并对值域离散后,可以直接用无旋treap维护每个值域的下标中序,那么修改就是简单的删除操作,查询也是简单的分离操作。时间复杂度$O(n\log n)$。
法四代码:
/*Code by 520 -- 10.28*/
#include<bits/stdc++.h>
#define il inline
#define ll long long
#define RE register
#define For(i,a,b) for(RE int (i)=(a);(i)<=(b);(i)++)
#define Bor(i,a,b) for(RE int (i)=(b);(i)>=(a);(i)--)
using namespace std;
const int N=;
int n,m,a[N],*q[N<<],cnt;
int ch[N][],rt[N],rnd[N],date[N],siz[N];
struct node{
int l,r,x;
}t[N];
char opt[N][]; int gi(){
int a=;char x=getchar();
while(x<''||x>'') x=getchar();
while(x>=''&&x<='') a=(a<<)+(a<<)+(x^),x=getchar();
return a;
} il bool cmp(int *a,int *b){return *a<*b;} il int newnode(int v){
++cnt;
siz[cnt]=,date[cnt]=v,rnd[cnt]=rand();
return cnt;
} il void up(int rt){siz[rt]=siz[ch[rt][]]+siz[ch[rt][]]+;} int merge(int x,int y){
if(!x||!y) return x+y;
if(rnd[x]<rnd[y]) {ch[x][]=merge(ch[x][],y),up(x);return x;}
else {ch[y][]=merge(x,ch[y][]),up(y);return y;}
} void split(int rt,int v,int &x,int &y){
if(!rt) {x=y=;return;}
if(date[rt]<=v) x=rt,split(ch[rt][],v,ch[x][],y),up(x);
else y=rt,split(ch[rt][],v,x,ch[y][]),up(y);
} il void ins(int k,int v){
int x,y; split(rt[k],v,x,y),rt[k]=merge(merge(x,newnode(v)),y);
} il void del(int k,int v){
int x,y,z; split(rt[k],v,x,y),split(x,v-,x,z),rt[k]=merge(x,y);
} int main(){
srand(time());
n=gi(),m=gi();
For(i,,n) a[i]=gi(),q[++cnt]=&a[i];
For(i,,m){
scanf("%s",opt[i]);
if(opt[i][]=='Q') t[i]=node{gi(),gi(),gi()},q[++cnt]=&t[i].x;
else t[i]=node{gi(),gi(),},q[++cnt]=&t[i].r;
}
sort(q+,q+cnt+,cmp); int lst=-,tot=;
For(i,,cnt) if(*q[i]!=lst) lst=*q[i],*q[i]=++tot; else *q[i]=tot;
cnt=;
For(i,,n) ins(a[i],i);
For(i,,m){
if(opt[i][]=='Q') {
int x,y,z;
split(rt[t[i].x],t[i].r,x,y),split(x,t[i].l-,x,z);
printf("%d\n",siz[z]);
rt[t[i].x]=merge(merge(x,z),y);
}
else {
int x,y,z,u=t[i].l,v=t[i].r;
del(a[u],u),a[u]=v,ins(a[u],u);
}
}
return ;
}
P2464 [SDOI2008]郁闷的小J的更多相关文章
- 洛谷P2464 [SDOI2008] 郁闷的小j [分块]
题目传送门 郁闷的小j 题目描述 小J是国家图书馆的一位图书管理员,他的工作是管理一个巨大的书架.虽然他很能吃苦耐劳,但是由于这个书架十分巨大,所以他的工作效率总是很低,以致他面临着被解雇的危险,这也 ...
- 2018.09.26 洛谷P2464 [SDOI2008]郁闷的小J(map+vector)
传送门 本来出题人出出来想考数据结构的. 但是我们拥有map+vector/set这样优秀的STL,因此直接用map离散化,vector存下标在里面二分找答案就行了. 代码: #include< ...
- 洛谷P2464 [SDOJ2008]郁闷的小J
洛谷P2464 [SDOJ2008]郁闷的小J 题目描述 小J是国家图书馆的一位图书管理员,他的工作是管理一个巨大的书架.虽然他很能吃苦耐劳,但是由于这个书架十分巨大,所以他的工作效率总是很低,以致他 ...
- [SDOI2008]郁闷的小J(分块)
[SDOI2008]郁闷的小J 题目描述 小J是国家图书馆的一位图书管理员,他的工作是管理一个巨大的书架.虽然他很能吃苦耐劳,但是由于这个书架十分巨大,所以他的工作效率总是很低,以致他面临着被解雇的危 ...
- fhqtreap - Luogu 2464 [SDOI2008]郁闷的小J
[SDOI2008]郁闷的小JJ 题目描述 小J是国家图书馆的一位图书管理员,他的工作是管理一个巨大的书架.虽然他很能吃苦耐劳,但是由于这个书架十分巨大,所以他的工作效率总是很低,以致他面临着被解雇的 ...
- 【洛谷 P2464】[SDOI2008]郁闷的小J(线段树)
题目链接 这题我很久之前用分块写过,没写出来.. 今天又看到了,于是下决心把这题做出来. 这次我用线段树写的,直接对每本书的编号Hash一下然后离散化然后各建一棵线段树,维护当前编号在某个位置有没有书 ...
- 山东省选 郁闷的小J
小J是国家图书馆的一位图书管理员,他的工作是管理一个巨大的书架.虽然他很能吃苦耐劳,但是由于这个书架十分巨大,所以他的工作效率总是很低,以致他面临着被解雇的危险,这也正是他所郁闷的. 具体说来,书架由 ...
- 【山东省选2008】郁闷的小J 平衡树Treap
小J是国家图书馆的一位图书管理员,他的工作是管理一个巨大的书架.虽然他很能吃苦耐劳,但是由于这个书架十分巨大,所以他的工作效率总是很低,以致他面临着被解雇的危险,这也正是他所郁闷的.具体说来,书架由N ...
- HUST-1407 郁闷的小J
离线做法:分别处理每个编号上的各种询问和操作,接着就能用树状数组维护. #include <cstdlib> #include <cstdio> #include <cs ...
随机推荐
- 网易云音乐 歌词制作软件 BesLyric (最新版本下载)
导读 BesLyric , 一款专门制作 网易云音乐 LRC 滚动歌词的软件! 搜索.下载.制作 歌词更方便! 哈哈,喜欢网易云音乐,又愁于制作歌词的童鞋有福啦!Beslyric 为你排忧解难! 本文 ...
- linux内存源码分析 - 页表的初始化
本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 本文章中系统我们假设为x86下的32位系统,暂且不分析64位系统的页表结构. linux分页 linux下采用四 ...
- UVA1626 - Brackets sequence(区间DP--括号匹配+递归打印)
题目描写叙述: 定义合法的括号序列例如以下: 1 空序列是一个合法的序列 2 假设S是合法的序列.则(S)和[S]也是合法的序列 3 假设A和B是合法的序列.则AB也是合法的序列 比如:以下的都是合法 ...
- JQuery如何实现双击事件时不触发单击事件
单击和双击事件的执行顺序: 单击(click):mousedown,mouseout,click: 双击(dblclick):mousedown,mouseout,click , mousedown, ...
- IOS 上架到App Store被拒的常见问题总结
Guideline 2.3.3 - Performance - Accurate Metadata 2017年11月16日 上午12:52 发件人 Apple 2. 3 Performance: Ac ...
- [尝鲜]妈妈再也不用担心 dotnet core 程序发布了: .NET Core Global Tools
什么是 .NET Core Global Tools? Global Tools是.NET Core 2.1 中一个初次出现的特性.Global Tools提供了一种方法,让开发人员编写的.NET C ...
- CSS 列表实例
CSS 列表属性允许你放置.改变列表项标志,或者将图像作为列表项标志.CSS 列表属性(list)属性 描述list-style 简写属性.用于把所有用于列表的属性设置于一个声明中.list-styl ...
- 一文让你完全弄懂Stegosaurus
国内关于 Stegosaurus 的介绍少之又少,一般只是单纯的工具使用的讲解之类的,并且本人在学习过程中也是遇到了很多的问题,基于此种情况下写下此文,也是为我逝去的青春时光留个念想吧~ Stegos ...
- python基础学习笔记(五)
字符串基本操作 所有标准的序列操作(索引.分片.乘法.判断成员资格.求长度.取最小值和最大值)对字符串同样适用,前面已经讲述的这些操作.但是,请注意字符串都是不可变的. 字符串的方法: 字符串从str ...
- Linux内核分析 笔记五 扒开系统调用的三层皮(下) ——by王玥
(一)给MenuOs增加time和time-asm命令 更新menu代码到最新版 在main函数中增加MenuConfig 增加对应的Ttime和TimeAsm函数 make rootfs (二)使用 ...