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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

NOIP2017 列队——动态开点线段树

發布時間:2025/3/17 编程问答 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 NOIP2017 列队——动态开点线段树 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Description:

Sylvia 是一個熱愛學習的女♂孩子。

前段時間,Sylvia 參加了學校的軍訓。眾所周知,軍訓的時候需要站方陣。

Sylvia 所在的方陣中有n×m名學生,方陣的行數為?n,列數為?m。

為了便于管理,教官在訓練開始時,按照從前到后,從左到右的順序給方陣中 的學生從 1 到?n×m?編上了號碼(參見后面的樣例)。即:初始時,第?i?行第?j?列 的學生的編號是(i?1)×m+j。

然而在練習方陣的時候,經常會有學生因為各種各樣的事情需要離隊。在一天 中,一共發生了?q件這樣的離隊事件。每一次離隊事件可以用數對(x,y)(1xn,1ym)描述,表示第?x?行第?y?列的學生離隊。

在有學生離隊后,隊伍中出現了一個空位。為了隊伍的整齊,教官會依次下達 這樣的兩條指令:

  • 向左看齊。這時第一列保持不動,所有學生向左填補空缺。不難發現在這條 指令之后,空位在第?x?行第?m?列。

  • 向前看齊。這時第一行保持不動,所有學生向前填補空缺。不難發現在這條 指令之后,空位在第?n?行第?m?列。
  • 教官規定不能有兩個或更多學生同時離隊。即在前一個離隊的學生歸隊之后, 下一個學生才能離隊。因此在每一個離隊的學生要歸隊時,隊伍中有且僅有第?n?行 第?m?列一個空位,這時這個學生會自然地填補到這個位置。

    因為站方陣真的很無聊,所以 Sylvia 想要計算每一次離隊事件中,離隊的同學 的編號是多少。

    注意:每一個同學的編號不會隨著離隊事件的發生而改變,在發生離隊事件后 方陣中同學的編號可能是亂序的。

    Hint

    Solution

    一年前暴力敲了30pts

    一年后暴力敲了60pts

    沒什么長進啊

    還是不會正解。

    ?

    1.不懂樹狀數組

    2.不想寫平衡樹

    所以我們寫動態開點線段樹

    ?

    首先發現,對于x=1的點,可以想到對這個鏈開一棵長度為max(n,m)+q的線段樹。每次找第k個有數的地方,然后放到最后的位置。

    發現,每次向前對齊只有最后一列要動,

    向左看齊,只是當前的行會向左移動。

    所以,為了便于操作,我們開n+1棵線段樹,前n棵維護i行,1~m-1的答案

    最后一棵n+1,維護最后一列n個答案。

    ?

    然后我們就得到了一個優秀的MLE做法辣!~~

    所以就要動態開點線段樹。

    ?

    (因為我比較弱)所以簡單講解一下動態開點線段樹。

    發現,有的時候,線段樹需要維護的區間很大很大,但是實際用到的節點很少很少。

    那么,我們干脆就不要開這么多的節點,用到的時候再向內存要。

    也就是說,我們建立了一棵殘疾的線段樹,缺少很多枝葉,但是絕對夠用了。

    畫個圖大概理解一下(雖然也不太對)

    實心邊框的點都是我們申請內存給的,虛的點是沒用的。就算申請也不用,實在是浪費資源。

    所以,

    我們開局只有一個根,裝備葉子全靠給。

    例如我們要建立一個權值線段樹,但是在線操作不讓你離散化,值域又是inf級別的,

    像這樣,即使這個區間的范圍很大,但是如果詢問q比較少的話,我們只需要qloginf個節點,就可以辦到。

    ?

    (發現和主席樹有點像,但是省空間的思想還是有些不同的。)

    ?

    然后我們用動態開點線段樹來做這個題。

    線段樹根節點維護的區間是max(n,m)+q;

    開始每個線段樹甚至連根也不用建,需要的時候會建起來。

    每個線段樹節點記錄sz,子樹實際的人數大小。(開始的時候,只有1~n(m-1)是sz=r-l+1的)

    sz可以用一個函數處理。雖然并沒有這么多的葉子,但是實際上,建出這么多的葉子,也是這個sz(這也是能動態開點的條件)

    再記錄一個val(long long型需注意),記錄當前節點所代表的人的編號

    這個編號val只有在葉子節點才有用。

    ?

    其實每次詢問引起的變化是:樹x的第y個人走了,進入了樹n+1的末尾,樹n+1的第x走了,進入樹x的末尾。

    每次詢問,如果y==m就進入線段樹n+1查詢,否則進入線段樹x查詢,找到答案ans輸出

    查詢的時候,順便sz--,刪掉途經點的sz(就不用pushup了)

    把ans這個編號放進n+1線段樹的末尾(新開一個位置)

    同樣,途經sz++

    如果y!=m說明,第x棵線段樹最后進來一個人。就把n+1的第x個人查詢(刪除),放進線段樹x的末尾(新開一個位置)。

    這樣子,其實每棵線段樹根節點的sz都保持為m-1(或n)

    ?

    Code

    #include<bits/stdc++.h> #define mid ((l+r)>>1) using namespace std; typedef long long ll; const int N=3e5+5; const int M=1e7+2; ll n,m,q; struct node{int ls,rs;int sz;ll val; }t[M]; int id,tot; int rt[N]; ll now; int cur[N]; int up; int get(int l,int r){if(now==n+1){if(r<=n) return r-l+1;if(l<=n) return n-l+1;return 0;}if(r<m) return r-l+1;if(l<m) return m-l;return 0; } ll query(int &x,int l,int r,int c){if(!x){x=++tot;t[x].sz=get(l,r);if(l==r){if(now==n+1) t[x].val=l*m;else t[x].val=(now-1)*m+l;}}t[x].sz--;if(l==r) return t[x].val;if((!t[x].ls&&c<=get(l,mid))||c<=t[t[x].ls].sz) return query(t[x].ls,l,mid,c);else{if(!t[x].ls) c-=get(l,mid);else c-=t[t[x].ls].sz;return query(t[x].rs,mid+1,r,c);} } void upda(int &x,int l,int r,int to,ll d){if(!x){x=++tot;t[x].sz=get(l,r);if(l==r){t[x].val=d;}}t[x].sz++;if(l==r) return;if(to<=mid) return upda(t[x].ls,l,mid,to,d);else return upda(t[x].rs,mid+1,r,to,d); } int main() {scanf("%lld%lld%lld",&n,&m,&q);int x,y;ll ans;up=max(n,m)+q;while(q--){scanf("%d%d",&x,&y);if(y==m) now=n+1,ans=query(rt[now],1,up,x);else now=x,ans=query(rt[now],1,up,y);printf("%lld\n",ans);now=n+1;upda(rt[now],1,up,n+(++cur[now]),ans);if(y!=m){now=n+1;ans=query(rt[now],1,up,x);now=x;upda(rt[now],1,up,m-1+(++cur[now]),ans);}}return 0; }

    ?

    ?

    upda:2018.11.2

    感覺這個動態開點線段樹其實不算是典型的動態開點23333

    一般的線段樹都是區間表示連續一些下標之類。動態開點也是如此。

    但是這個做法的話,愣是把線段樹寫成了平衡樹的存儲方式。

    區間的長度僅僅代表的是預留空間。

    就是把許多點壓成了一個點。

    ?

    轉載于:https://www.cnblogs.com/Miracevin/p/9582412.html

    新人創作打卡挑戰賽發博客就能抽獎!定制產品紅包拿不停!

    總結

    以上是生活随笔為你收集整理的NOIP2017 列队——动态开点线段树的全部內容,希望文章能夠幫你解決所遇到的問題。

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