日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

HDU - 2871 Memory Control(线段树+区间合并)好题!

發布時間:2024/4/11 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 HDU - 2871 Memory Control(线段树+区间合并)好题! 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

題目鏈接:點擊查看

題目大意:給定n個內存和m個操作,分別是:

  • New x:從內存編號1開始向右查找,分配一個長度為x的空間,若找到輸出區間的首地址,否則輸出Reject New;
  • Free x:釋放包含x在內的連續區間,若釋放成功,輸出該區間的首地址和尾地址,否則輸出Reject Free;
  • Get x:從內存編號1開始向右查找,查找第x個連續區間內存,若找到輸出該區間的首地址,否則輸出Reject Get;
  • Reset:釋放所有內存(手動初始化)
  • 題目分析:

    先對這個題吐槽一下吧,本來以為昨天晚上做的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) 的代碼給放過去了。。

    其實正解也不難,瓶頸在于:

  • 維護一個有序序列,包括增加和刪除
  • 快速查詢第 k 大的數字
  • 用線段樹實現就好了,懶得重構代碼了,繼續復習數據庫去啦

    上代碼:

    #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(线段树+区间合并)好题!的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。