HDU 3436 Queue-jumpers (无旋treap)

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

题目传送门(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;
}

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值