题目链接
题意:
四种操作:
1. set s x 如果x不存在,将x插入,并设置其优先值为x,如果x存在,直接修改其优先值为x。
2. remove s 删除s
3. query s 询问优先值比s小的有多少个
4. undo d 撤销这次操作之前的d个操作
(感觉这题写好了,真能开发个自己的to-do list)
思路:
权值线段树r1,在优先级[1,1e9]的值域上,维护每个优先级出现的次数,单点修改+1/-1,区间查询[1,p-1],累加优先级比p小的字符串的个数
权值线段树r2,维护每个id的值域上,维护每个id的优先级,单点修改±dx,单点查询[id,id],直接返回叶子节点id的优先级
更详细的在注释里了
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5, INF=1e9+5;//INF 在[1,1e9]上建权值线段树
struct Node{
int l,r,sum;
}hjt[N*100];//OMG 两棵主席树 其中一棵一次询问可能修改两次 注意空间
int cnt,r1[N],r2[N];//两棵主席树 r1[i]代表i优先级有多少个字符串(权值线段树) r2[id]代表id字符串对应的优先级是多少(这个其实也是权值,但维护的不是个数了,是该id值对应的优先级)
int top;
map<string,int>mp;
//Q:为什么不用建树呢? A:因为两棵都是权值线段树!初始版本就都是0,直到有新结点插入
//Q:为什么权值[1,1e9]可以建主席树呢? A:时间复杂度qlogx q=1e5 x=1e9 log(1e9)=30 总:3e6 可以哒
//Q:为什么是线段树? r2完全可以不用区间操作啊 A:为了迁就r1,r1不是要优先级查比p小的有多少个嘛...
void update(int l,int r,int pre,int &now,int pos, int num){
//当前区间[l,r] 历史版本pre 当前版本now 修改:pos位置上的数+val
//对于r1树来说 这里维护的是权值 值域[1,1e9] pos位置的sum 代表pos数出现的次数
//对于r2树 范围[1,1e5](1e5个询问-可能的新串) pos串的sum 是它的优先级(修改优先级也是通过加减增量 不是直接改值)
hjt[now=++cnt]=hjt[pre];//pre版本 复制到一块新的内存池
hjt[now].sum+=num;//? 权值 新插入一个数 sum++ 为什么在这里不在叶子节点里呢 //这个线段树上的累加 对r2树其实没有意义 但也没关系 反正r2树是单点查询 询问路径上的值都用不到 只会用到叶子节点
if(l==r) return;
int m=l+r>>1;
if(pos<=m) update(l,m,hjt[pre].l,hjt[now].l,pos,num);
else update(m+1,r,hjt[pre].r,hjt[now].r,pos,num);
//Q: 话说,这个主席树的hjt[now].l... 这个 .l .r 是多少啊,从来没有给节点的 .l .r赋过值啊...
//A: 额 假如第一次操作set 5 2,[1,1e9]的区间不断二分,5最终会找到它的位置,在找到5之前,0号主席树、1号主席树的所有节点的l、r都是0(因为0号初始都是0,1号复制0号的),
//到了5叶子节点后回溯,now传的是引用,1号主席树上从[1,1e9]查询5的路径上的节点自底向上地就有值了,值是1号主席树叶节点5的编号(在整个N*100的主席树结点中的唯一的编号)
//Q:所以又引出个问题 主席树开多大?
//A: 天呐这个问题我暂时无法回答。。。但我知道为什么开N*40*2=N*80会RE了,因为r1树的set操作,如果id串不存在的话,会涉及两次修改,也就是每次询问最多有两次修改,r1主席树应该开2*qlogx,r2主席树正常开qlogx,一共是3qlogx,所以你开3*40*N妥妥够...
}
int query(int l,int r,int now,int ql,int qr){
//对于r1树 ql=1,qr=p-1 查比p优先值小(优先值越小 优先级越高)的字符串的个数 ans累加只在这里用到
//对于r2树 ql=qr=id 单点查询 查id串的优先级
if(ql>qr) return 0;//这句只为p==1的特殊情况
if(ql<=l&&r<=qr) return hjt[now].sum;
int m=l+r>>1;
int ans=0;
if(ql<=m) ans+=query(l,m,hjt[now].l,ql,qr);
if(qr>m) ans+=query(m+1,r,hjt[now].r,ql,qr);
return ans;
}
inline int getid(string s){
if(mp[s]) return mp[s];
return mp[s] = ++top ;
}
int main(){
int q;
scanf("%d",&q);
string s;
int x,id,p;
for(int i=1;i<=q;++i){
r1[i]=r1[i-1],r2[i]=r2[i-1];
//Q:为什么后面update操作pre和now版本可以都写成r1[i]呢? A:因为你在这里复制过旧版了呀 你写r1[i-1],r1[i]也是可以的(除了要修改两次的r1树 set 先-1再+1 修改两次)(然后在update里r1[i]会被赋予新的节点编号)
cin>>s;
//每次先到r2树上查id字符串的优先级 再做修改
//优先级x的范围是[1,1e9] 所以查询区间是[1,INF]
if(s[0]=='s'){
cin>>s>>x;
id=getid(s);
p=query(1,INF,r2[i],id,id);//到r2树上查询id的优先级 得到它优先级是p 为什么区间是[1,INF]呢?这关区间什么事呢?//其实区间应该是[1,max(id)],
if(p) update(1,INF,r1[i],r1[i],p,-1);//r1[i]这棵树 的 p优先级的个数 -1 (因为p变成x了!)
update(1,INF,r1[i],r1[i],x,1);//r1树 x优先级的数量 +1 //这里注意!!! 只有这里!只有这里不能写成update(1,INF,r1[i-1],r1[i], (其他update都可以!),因为r1有可能在if里面减过1,有了个新版本,新版本编号传回给r1[i]了,再修改必须在r1[i]上修改,这才是正确的pre版本编号
update(1,INF,r2[i],r2[i],id,x-p);//r2树 id串的优先级 变为x(本来是p 加上x-p)
}else if(s[0]=='q'){
cin>>s;
id=getid(s);
p=query(1,INF,r2[i],id,id);
if(p==0) puts("-1");
else printf("%d\n",query(1,INF,r1[i],1,p-1));//r1树 查优先级是[1,p-1]的 有多少 求和
}else if (s[0]=='u'){//Q:那undo不是会独占一天的版本?题目不是说 not including the day of this operation (undo) A:是的啊,undo就是有一个新版本,题目意思是,它撤销的的前d天,这个d不包括今天!d days(not including the day(重点:d days not include the day!) of this operation)
cin>>x;
r1[i]=r1[i-1-x];//第i天 撤销此前x天的操作 回到第i-1-x天的版本
r2[i]=r2[i-1-x];
}else{
cin>>s;
id=getid(s);
p=query(1,INF,r2[i],id,id);//Q:r2[i]这个版本有了吗? A:有啊 循环最初不就复制了一份旧版嘛
if(!p) continue;
update(1,INF,r1[i],r1[i],p,-1);//p优先级的数量 -1
update(1,INF,r2[i],r2[i],id,-p);//id串的优先级 -p 变为0
}
}
}
//天呐我学习的方式是自问自答!......
PS:两棵主席树空间开N*80 RE了,N*100才过。
我希望我写的东西,是被沙子掩埋的明珠~ 我会耐心地把沙子一点点拂去~