题目传送门(HDU3436)
解题思路
题目的意思是位于一个[1,n]的序列有三种操作,操作一把一个数放到队首,操作二询问某个数现在的位置,操作三询问现在排名为k的数是谁
Part I 口胡
(第一部分可以跳过)
一开始的愚蠢思路是这样的:
用一棵平衡树T1维护进行过top操作的数的序列,另一棵平衡树T2维护top操作过的数的值排序
①每次top,如果这个数在T1中,找出他的排名,然后提取出他,把它放到平衡树的开头,对于不在T1上的,直接放到开头即可。
O(logN)
②每次询问x的位置,如果在T1中,直接T1中获取,否则就在T2中二分查找一下比x大的数,这些数是比x大的且位置比靠前的,那么x的实际位置就是原本x靠后这么几位
O(logN)
③每次询问排名为x,如果T1中有x个数了,那么直接T1中获取即可
否则就比较麻烦了,需要二分答案p,每次判断都需要T2中二分查找比P大的数,复杂度O(logN*logN)
由于常数还挺大,操作三这边直接整超时了。
Part II AC
找题解发现了离散化区间的蛇皮操作,把[1,1e8]的区间直接维护了,然后就变成了一棵平衡树的基础操作。
具体请看我写满注释的代码,
然后这题我还是找了蛮久的bug的,然后就写了一个比较垃圾的生成方便debug的随机数据的代码,是在找不到自己代码错误的可以找份正确代码,生成点随机数据和自己的结果对拍一下。(对拍可以去这个网站>>>Here)
大概没啥问题的数据生成代码(谁知道呢)
#include<bits/stdc++.h>
using namespace std;
#define INF 100000000
#define maxINT ((1LL<<31)-1)
char op[][10] = {"Top","Query","Rank"};
int Rand(){
int seed = rand();
int seed2 = rand();
return (int)(1LL*seed*seed2%maxINT);
}
int main(){
freopen("C:/Users/DELL/Desktop/input.txt", "w", stdout);//修改为自己电脑上想要生成随机的路径,以及文件的名字,比如: D:/学习资料/日本动作片/AC.txt
srand(time(0));
int T = 100;
printf("%d\n",T);
while (T--){
//n:区间范围,最多一亿,q:询问次数,最多10w次
int n,q = 10;
n = Rand()%(INF+1);
if (!n) n = 1;
printf("%d %d\n",n,q);
while (q--){
int oop = Rand()%3;
int pos = Rand()%(n+1);
if (!pos) pos = 1;
printf("%s %d\n",op[oop],pos);
}
}
return 0;
}
本题代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define for1(i,a,b) for (int i=a;i<=b;i++)
#define for0(i,a,b) for (int i=a;i<b;i++)
#define pt(x) printf("%s = %I64d\n",#x,(ll)(x))
#define d(x) printf("OK %s!",#x)
#define lson ch[rt][0]
#define rson ch[rt][1]
#define maxINT ((1LL<<31)-1)
const int N = 1e5+5;
const int M = 2e5+10;
char op[N][6];//操作的名称
int ob[N];//操作涉及的节点
int mp[M];//离散化后 区间/节点 下标映射 平衡树节点下标
int dis[M],tot;
int st[M],ed[M],idx;//离散化后的节点映射实际区间
struct fhq_treap
{
int ch[M][2];
int pri[M];
int poi[M];//平衡树下标映射离散化后的 点/区间 的下标,用于获得该点的st,ed
int size[M];//树的大小
int cnt[M];//该节点的大小
int fa[M];
int sta[M];
int root,sz;
void init(){
root = 0;
sz = 1;
}
int rand(){//自己实现rand操作,据说更快
static int seed = 1433223;
return seed = (1LL*seed*233%maxINT);
}
int newnode(int p){
ch[sz][0] = ch[sz][1] = 0;
pri[sz] = rand();
poi[sz] = p;
size[sz] = cnt[sz] = ed[p]-st[p]+1;
fa[sz] = 0;//一定要初始化呀
mp[p] = sz;
return sz++;
}
void push_up(int rt){
size[rt] = size[lson] + size[rson] + cnt[rt];
if (lson) fa[lson] = rt;
if (rson) fa[rson] = rt;
}
int merge(int x,int y){
if (!x || !y) return x+y;
else {
if (pri[x]>pri[y]){
ch[x][1] = merge(ch[x][1],y);
push_up(x);
return x;
}
else {
ch[y][0] = merge(x,ch[y][0]);
push_up(y);
return y;
}
}
}
void split(int rt,int k,int&x,int&y){//分裂出排名前k的
if (!rt) x = y = 0;
else {
if (k>=size[lson]+cnt[rt]){
x = rt;
split(ch[rt][1],k-size[lson]-cnt[rt],ch[x][1],y);
}
else {
y = rt;
split(ch[rt][0],k,x,ch[y][0]);
}
push_up(rt);
}
}
int getrank(int per){//值为x的点的排名
int p = mp[binary_search(per)];//p = mp[值为per的点离散化后的下标] = per在平衡树中哪个节点
if (p==root) return size[ch[root][0]]+1;
int ans = size[ch[p][0]] + 1;
while (fa[p]){
int f = fa[p];
if (ch[f][1]==p) ans += size[ch[f][0]] + cnt[f];//当前是右子树,那么左子树和父亲都比自己小
p = f;
}
return ans;
}
int getkth(int rt,int k){//查询排名第k的
if (k<=size[lson]) return getkth(lson,k);
if (k - size[lson] - cnt[rt]<=0) return st[poi[rt]] + k-size[lson]-1;
else return getkth(rson,k-size[lson]-cnt[rt]);
}
void build(int n){//模仿笛卡尔树O(N)建树,不懂去做BZOJ1500
int tot = -1;
for1(i,1,n){
int now = newnode(i);
int nowtot = tot;
while (tot>=0 && pri[now] > pri[sta[tot]]) push_up(sta[tot--]);
if (tot>=0) ch[sta[tot]][1] = now;
if (tot<nowtot) ch[now][0] = sta[tot+1];
sta[++tot] = now;
}
while (tot>=0) push_up(sta[tot--]);
root = sta[0];
}
int binary_search(int x){//在离散化后的数组寻找映射值为x的下标
int l = 1,r = idx;
int mid;
while (l<=r){
mid = l+r>>1;
if (st[mid]==x) return mid;
if (ed[mid]<x) l = mid+1;
else r = mid-1;
}
}
void insert(int p){//将值为p的点放到平衡树第一位
int v = getrank(p);
int x,y,z;
split(root,v-1,x,y);
split(y,1,y,z);
fa[y] = fa[x] = fa[z] = 0;//这个很重要,因为xyz其中一个为必定为根,他没有父亲所以他的fa没有父亲可以帮他push_up更新正确
root = merge(merge(y,x),z);//然而我把上一行去掉还是能AC,爷吐了
}
//下面都是调试的时候用的
void DE(){dfs1(root);}
void dfs1(int rt){
if (!rt) return ;
dfs1(lson);
printf("node %d :[%d,%d]\n",rt,st[rt],ed[rt]);
dfs1(rson);
}
void BUG(){dfs2(root);}
void dfs2(int rt){
if (!rt) return ;
printf("node %d two_son: %d %d\n",rt,lson,rson);
dfs2(lson);
dfs2(rson);
}
}ftp;
int main()
{
//freopen("C:/Users/DELL/Desktop/input.txt", "r", stdin);
//freopen("C:/Users/DELL/Desktop/My.txt", "w", stdout);
int T,ica = 1;
scanf("%d",&T);
while (T--){
printf("Case %d:\n",ica++);
ftp.init();
tot = 0;
idx = 0;
int n,q;
scanf("%d %d",&n,&q);
dis[tot++] = 0;
for0(i,0,q){
scanf("%s %d",op[i],&ob[i]);
if (op[i][0]!='R') dis[tot++] = ob[i];
}
dis[tot++] = n;
sort(dis,dis+tot);
/*排好序后所有点进行离散化*/
for0(i,1,tot){
if (dis[i]!=dis[i-1]){
if (dis[i]-dis[i-1]>1) st[++idx] = dis[i-1]+1,ed[idx] = dis[i]-1;//两个点之间有区间的话,这段区间看做一个点
st[++idx] = dis[i];//每个点也看做了区间
ed[idx] = dis[i];
}
}
ftp.build(idx);//ftp.DE();
for0(i,0,q){
if (op[i][0]=='T') ftp.insert(ob[i]);
if (op[i][0]=='Q') {
printf("%d\n",ftp.getrank(ob[i]));
}
if (op[i][0]=='R') printf("%d\n",ftp.getkth(ftp.root,ob[i]));
//printf("op%d:\n",i+1);ftp.DE();ftp.BUG();
}
}
return 0;
}

这篇博客介绍了如何使用无旋Treap解决HDU3436题目,该题目涉及在序列中进行特定操作并查询相关位置。作者首先分享了最初的错误思路,即使用两棵平衡树分别维护操作过的数的序列和值排序,但这种方法在处理某些操作时效率低下。随后,作者揭示了解决问题的关键在于离散化区间,并通过无旋Treap实现基础操作,从而提高了算法效率。文章还提供了代码和用于调试的随机数据生成方法。
877

被折叠的 条评论
为什么被折叠?



