HDU - 2871 Memory Control(线段树+区间合并)好题!
題目鏈接:點擊查看
題目大意:給定n個內存和m個操作,分別是:
題目分析:
先對這個題吐槽一下吧,本來以為昨天晚上做的hotle那個題就已經很惡心了,寫的代碼又臭又長,結果今天這個題刷新了我對線段樹的認知,這個題暫時讓他當我遇到過的最惡心的題目應該沒問題了。從早晨九點不到一直做到下午一點多,被惡心的不要不要的,都有點懷疑自己了。
對于上述四個操作挨個分析吧:
對于New這個操作,簡直和hotle那個題目的那個一模一樣啊,就是讓求最左端符合條件的區間,我們用區間合并就可以實現,在update函數和pushup函數中維護ll,rr和all三個變量的值,這三個值分別代表從區間左端點開始的最長連續長度,從區間右端點開始的最長連續長度和區間中的最長連續長度,維護好這三個值后再在query函數中依次查找符合條件的區間,若滿足向下遞歸,若得到答案則向上返回答案,都是套路了,一會看代碼就好。
對于Free操作,有個難點就是如何找到連續區間的首地址和尾地址,這里我的方法是在線段樹的每個節點中,維護兩個變量:st和ed,分別記錄該節點所屬的連續區間中的首地址與尾地址,那么也就引出了我們需要對于每個Free的x求出所對應的節點編號才能隨意訪問其節點,這里我用的一個getID的函數,類比于點查詢的操作向下遞歸,只不過返回值改成了該點的節點編號,時間復雜度為logn,畢竟是二分查找嘛。。那么現在我們已經求出了該節點的編號,就可以訪問他的首地址和尾地址了,那么接下來需要考慮的是如何初始化以及維護這兩個變量,因為這個題目中的n個內存都是大于0的,我們可以將其設置為0,即st=ed=0,代表該節點沒有位于連續內存中,當用update函數更新一段區間為連續內存是,將這段內存的首地址和尾地址都分別設置為區間的左右端點,等更新的時候用pushdown函數向下傳遞就好,為什么不用向上傳遞呢?因為當用update函數更新的時候,遇到符合條件的區間就不會向下遞歸了,所以這段區間就已經滿足條件了,我們只需要將這段區間下面的節點都更新上首尾地址即可。
對于Get操作,網上有一種很麻煩但跑起來很快的方法,我看了半天還是沒怎么看懂,大體意思就是在節點中在維護一個size變量,用來記錄當前區間中含有多少個連續的內存,這樣的話就可以直接對size進行二分查找(也是類似于點查詢的一種),找到具體的節點然后返回首地址即可,這里我是看懂了,只不過在free函數里面每次釋放節點都需要更新size的值,這里我不太會,加上關于size的傳遞我也被繞暈了。。感興趣的話可以去別人家的博客學習一下。關于這個題,想要實現這個操作我選擇了用時間付出點代價,用了stl中的set類,主要是因為set可以實現自動排序,每次遍歷一邊找到第x個數,直接輸出即可,而在保存的時候也是直接插入即可,他在內部會自動排序,free函數中釋放后也只需要刪除即可,其余的操作就不用我們管了,這樣Get操作也變得簡單易實現了。
最后就剩下了一個Reset操作了,這個操作不用多說了吧,直接用upset更新一下就好了
還有注意一下輸出格式
其實用stl一開始還是擔心會被卡時間的,比如給出5e5個內存,然后5e5個操作,每個操作都是刪除一個數然后增加一個數,感覺很有可能超時,或者是給出5e5個數,每次都Get最后一個,那時間復雜度就變成了至少1e10了,不過還好謝天謝地這個題目沒有卡這些點,讓本蒟蒻順利鉆空子過了。。?
2021.6.27 UPDATE:
考試周不想復習突然看到了這個題目,讀了一遍代碼發現題目數據水了,把之前寫的 O(n*n) 的代碼給放過去了。。
其實正解也不難,瓶頸在于:
用線段樹實現就好了,懶得重構代碼了,繼續復習數據庫去啦
上代碼:
#include<iostream> #include<cstdio> #include<string> #include<cstring> #include<algorithm> #include<stack> #include<queue> #include<map> #include<sstream> #include<set> #include<cmath> using namespace std;typedef long long LL;const int inf=0x3f3f3f3f;const int N=5e4+100;set<int>vis;set<int>::iterator it;struct Node {int l,r;//左右區間端點int ll,rr,all;//從左端點開始的最長連續長度,從右端點開始的最長連續長度,區間內最長連續長度int st,ed;//首地址和尾地址int lazy;//懶標記int mid(){return (l+r)>>1;}int cal_len(){return r-l+1;}void update_len(){ll=rr=all=lazy*cal_len();} }tree[N<<2];void build(int k,int l,int r) {tree[k].l=l;tree[k].r=r;tree[k].all=tree[k].ll=tree[k].rr=tree[k].cal_len();tree[k].st=tree[k].ed=0;tree[k].lazy=-1;if(l==r)return;int mid=tree[k].mid();build(k<<1,l,mid);build(k<<1|1,mid+1,r); }void pushup(int k) {tree[k].ll=tree[k<<1].ll;tree[k].rr=tree[k<<1|1].rr;if(tree[k].ll==tree[k<<1].cal_len())tree[k].ll+=tree[k<<1|1].ll;if(tree[k].rr==tree[k<<1|1].cal_len())tree[k].rr+=tree[k<<1].rr;tree[k].all=max(tree[k<<1].rr+tree[k<<1|1].ll,max(tree[k<<1].all,tree[k<<1|1].all)); }void pushdown(int k) {tree[k<<1].st=tree[k<<1|1].st=tree[k].st;tree[k<<1].ed=tree[k<<1|1].ed=tree[k].ed;tree[k<<1].lazy=tree[k<<1|1].lazy=tree[k].lazy;tree[k].lazy=-1;tree[k<<1].update_len();tree[k<<1|1].update_len(); } void update(int k,int l,int r,int val,int flag)//k是節點編號,l和r是左右區間,val代表是否被占用,1代表未被占用,0代表被占用、flag和val的意義相似,1代表被占用,0代表未被占用 {if(tree[k].l>r||tree[k].r<l)return;if(tree[k].l>=l&&tree[k].r<=r){tree[k].lazy=val;tree[k].update_len();if(flag){tree[k].st=l;tree[k].ed=r;}else{tree[k].st=tree[k].ed=0;}return;}if(tree[k].lazy!=-1)pushdown(k);update(k<<1,l,r,val,flag);update(k<<1|1,l,r,val,flag);pushup(k); }int getID(int k,int pos)//通過節點位置得到節點編號的函數 {if(tree[k].l==tree[k].r)return k;if(tree[k].lazy!=-1)pushdown(k);int mid=tree[k].mid();if(mid>=pos)return getID(k<<1,pos);elsereturn getID(k<<1|1,pos); }int query(int k,int len)//查詢可用空間并返回其首地址 {if(tree[k].l==tree[k].r)if(len==1)return tree[k].ll;if(tree[k].lazy!=-1)pushdown(k);if(tree[k<<1].all>=len){return query(k<<1,len);}else if(tree[k<<1].rr+tree[k<<1|1].ll>=len){return tree[k<<1].r-tree[k<<1].rr+1;}else if(tree[k<<1|1].all>=len){return query(k<<1|1,len);} }int main() { // freopen("input.txt","r",stdin);int n,m;while(scanf("%d%d",&n,&m)!=EOF){vis.clear();//多組輸入記得初始化build(1,1,n);char s[10];while(m--){scanf("%s",s);if(s[0]=='N'){int len;scanf("%d",&len);if(len>tree[1].all)cout<<"Reject New"<<endl;else{int temp=query(1,len);printf("New at %d\n",temp);update(1,temp,temp+len-1,0,1);vis.insert(temp);}}else if(s[0]=='R'){cout<<"Reset Now"<<endl;update(1,1,n,1,0);vis.clear();}else if(s[0]=='G'){int x;scanf("%d",&x);int cnt=0;for(it=vis.begin();it!=vis.end();it++){cnt++;if(cnt==x)break;}if(cnt<x)cout<<"Reject Get"<<endl;elseprintf("Get at %d\n",*it);}else if(s[0]=='F'){int x;scanf("%d",&x);int cnt=getID(1,x);if(tree[cnt].st==0)cout<<"Reject Free"<<endl;else{printf("Free from %d to %d\n",tree[cnt].st,tree[cnt].ed);vis.erase(tree[cnt].st);update(1,tree[cnt].st,tree[cnt].ed,1,0);}}}cout<<endl;//記得輸出一個回車防止PE}return 0; }總結
以上是生活随笔為你收集整理的HDU - 2871 Memory Control(线段树+区间合并)好题!的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: HDU - 3667 Hotel(线段树
- 下一篇: HDU - 5475 An easy p