POJ - 2828 Buy Tickets(线段树+思维/Splay+模拟)
題目鏈接:點擊查看
題目大意:給出n個人,一個隊列,一開始隊列是空的,每個人加入的時候都會插入到指定位置pos的后面,即插隊,問最后隊列的排列情況是怎么樣的
題目分析:一開始很難想到是線段樹的題目,畢竟這題目正兒八經的方法還能用樹狀數組,數據范圍小點還可以貪心或暴力都可以解決,可是這個題的n是2e5,如果暴力n*n肯定就爆掉了,只能優化成logn才行。
不多廢話了,這個題目其實就是經典題目:求1~n中第K大的樹 這個題目的一種變形,求1~n中第K大的話看到第K大的第一反應肯定是主席樹,因為前幾天剛學了這么高大上的高級數據結構,所以有點蠢蠢欲動的想敲板子試一試,話說回來,主席樹解決的是隨機區間[l,r]內的第K大的數,而這個題目的區間是給定了,[1,n],所以不用給自己找麻煩,我們只需要建立一個線段樹,線段樹的范圍是1~n,維護的是區間內有多少個點還沒被用過,用sum來表示,那么我們如果想找第K大的數,可以用query函數二分去查找相應的點,如果找到了就返回位置(位置就是這個數字本身),找不到可以返回-1(當然這個題的數據保證了都能找到相應的位置),記得每次詢問的時候都要將這條鏈上的sum減一,因為我們要找的這個點馬上就要用掉了,我們的sum維護的是還沒有用過的點,這樣每次詢問都會修改logn個點,時間復雜度也就變為logn級別的了。
上面是講了如何求1~n中第k大的數,那么和這個題目又有什么關系呢?這個題的題意我稍微轉換一下,每個人插入的時候不都是插入到pos后面的那個位置嘛,那么不就是,當這個人插入到隊列中時,是第(pos+1)大的,而后面插進隊伍的人只會影響到前面的人,因為前面已經排好的序列被插隊的人打亂了嘛,所以我們從最后一個人開始倒著遍歷,因為最后一個人插入隊伍之后,就沒有人影響他的序列了,所以這個人就是n個人隊伍中的第(pos+1)大的人,在1~n中找到第(pos+1)大的數就是第n個人的序號了,這個時候將第n個人刪除掉,因為已經處理完了嘛,刪除掉第n個人后,第n-1個人便成了最后一個人,再找到第n-1個人的第(pos+1)的位置就是第n-1個人的序號了,這樣以此類推從后往前遍歷,就能以nlogn的時間復雜度完成任務啦
2020.3.8更新:
這個題目在練習Splay時再次碰到,發現可以直接用Splay模擬,只不過常數比較大,不過問題不大
每次插隊時,如果隊列為空記得特判一下,如果pos[ i?] == 0 時也要特判一下 ,此時新插入的人在最前面,也就是可以直接作為根節點,其余情況我們找出第 pos[ i ] 的結點,將其旋轉到根節點,然后變為當前插入節點的左子樹就好了,最后dfs中序遍歷就是答案了,這個題需要對insert按照理解重寫,還是有一點小難度的
上代碼吧:
線段樹:
#include<iostream> #include<cstdio> #include<string> #include<cstring> #include<algorithm> #include<stack> #include<queue> #include<map> #include<cmath> #include<set> #include<sstream> using namespace std;typedef long long LL;const int inf=0x3f3f3f3f;const int N=2e5+100;struct node {int pos,val; }a[N];struct Node {int l,r,sum;//sum記錄的是還沒有被標記過的人有多少個 }tree[N<<2];void build(int k,int l,int r) {tree[k].l=l;tree[k].r=r;tree[k].sum=r-l+1;//初始化時sum初始化為區間長度if(l==r)return;int mid=l+r>>1;build(k<<1,l,mid);build(k<<1|1,mid+1,r); }int query(int k,int num) {tree[k].sum--;//查詢的同時別忘了減一if(tree[k].l==tree[k].r)return tree[k].l;if(tree[k<<1].sum>=num)//如果當前的數字比左區間內的總數字數目要小,那么該數字必定在左區間return query(k<<1,num);else//否則就去右區間內查找return query(k<<1|1,num-tree[k<<1].sum/*記得這里如果去了右區間,那么要查找的第K大的數就變為了第(K-左兒子數目)大了,想一想,是不是?*/); }int ans[N];int main() { // freopen("input.txt","r",stdin);int n;while(scanf("%d",&n)!=EOF){for(int i=1;i<=n;i++){scanf("%d%d",&a[i].pos,&a[i].val);a[i].pos++;//每個pos都加一,來表示屬于第K大}build(1,1,n);for(int i=n;i>=1;i--){int pos=query(1,a[i].pos);ans[pos]=a[i].val;}printf("%d",ans[1]);for(int i=2;i<=n;i++)printf(" %d",ans[i]);cout<<endl;}return 0; }Splay:
#include<iostream> #include<cstdio> #include<string> #include<ctime> #include<cmath> #include<cstring> #include<algorithm> #include<stack> #include<climits> #include<queue> #include<map> #include<set> #include<sstream> using namespace std;typedef long long LL;typedef unsigned long long ull;const int inf=0x3f3f3f3f;const int N=2e5+100;class Splay {public:int ch[N][2],f[N],size[N],key[N];//size:子樹大小,cnt:當前節點出現次數,key:權值 int val[N];int sz,root;inline void clear(int x){ch[x][0]=ch[x][1]=f[x]=size[x]=key[x]=0;}inline bool get(int x){return ch[f[x]][1]==x;}inline void update(int x){if(x){size[x]=1;if(ch[x][0]) size[x]+=size[ch[x][0]];if(ch[x][1]) size[x]+=size[ch[x][1]];}}inline void rotate(int x){int old=f[x],oldf=f[old],whichx=get(x);ch[old][whichx]=ch[x][whichx^1]; f[ch[old][whichx]]=old;ch[x][whichx^1]=old; f[old]=x;f[x]=oldf;if(oldf)ch[oldf][ch[oldf][1]==old]=x;update(old); update(x);}inline void splay(int x){for(int fa;fa=f[x];rotate(x))if(f[fa])rotate((get(x)==get(fa))?fa:x);root=x;}inline void insert(int x,int v){clear(sz+1);if(root==0)//空樹 {sz++; ch[sz][0]=ch[sz][1]=f[sz]=0; root=sz; size[sz]=1; key[sz]=x; val[sz]=v;return;}if(x==0){int now=root;while(ch[now][0])now=ch[now][0];sz++;ch[sz][0]=ch[sz][1]=0;f[sz]=now;size[sz]=1;val[sz]=v;update(now);splay(sz);return;}int now=findx(x);splay(now);sz++;ch[sz][0]=now;f[now]=sz;ch[sz][1]=ch[now][1];f[ch[now][1]]=sz;ch[now][1]=0;val[sz]=v;root=sz;update(now);update(sz);return;}inline int findx(int x)//找到排名為x的點{int now=root;while(1){if(ch[now][0]&&x<=size[ch[now][0]])now=ch[now][0];else{int temp=(ch[now][0]?size[ch[now][0]]:0)+1;if(x<=temp) return now;x-=temp; now=ch[now][1];}}}inline int pre()//小于某個數的最大值 {int now=ch[root][0];while(ch[now][1]) now=ch[now][1];return now;}inline int next()//大于某個數的最小值 {int now=ch[root][1];while(ch[now][0]) now=ch[now][0];return now;}void dfs(int now){if(!now)return;dfs(ch[now][0]);printf("%d ",val[now]);dfs(ch[now][1]);} }tree;int main() { #ifndef ONLINE_JUDGE // freopen("input.txt","r",stdin); // freopen("output.txt","w",stdout); #endif // ios::sync_with_stdio(false);int n;while(scanf("%d",&n)!=EOF){tree.sz=tree.root=0;for(int i=1;i<=n;i++){int pos,val;scanf("%d%d",&pos,&val);tree.insert(pos,val);}tree.dfs(tree.root);putchar('\n');}return 0; }?
總結
以上是生活随笔為你收集整理的POJ - 2828 Buy Tickets(线段树+思维/Splay+模拟)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: HDU - 3397 Sequence
- 下一篇: CodeForces - 160E Bu