Description:
Sylvia 是一個熱愛學習的女♂孩子。
前段時間,Sylvia 參加了學校的軍訓。眾所周知,軍訓的時候需要站方陣。
Sylvia 所在的方陣中有n×m名學生,方陣的行數為 n,列數為 m。
為了便於管理,教官在訓練開始時,按照從前到后,從左到右的順序給方陣中 的學生從 1 到 n×m 編上了號碼(參見后面的樣例)。即:初始時,第 i 行第 j 列 的學生的編號是(i−1)×m+j。
然而在練習方陣的時候,經常會有學生因為各種各樣的事情需要離隊。在一天 中,一共發生了 q件這樣的離隊事件。每一次離隊事件可以用數對(x,y)(1≤x≤n,1≤y≤m)描述,表示第 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
一般的線段樹都是區間表示連續一些下標之類。動態開點也是如此。
但是這個做法的話,愣是把線段樹寫成了平衡樹的存儲方式。
區間的長度僅僅代表的是預留空間。
就是把許多點壓成了一個點。
