NOIP2017 列隊 動點線段樹
題目連結:列隊
主要思路:
和前面的思路有些相似。
首先,若用陣列存地圖(即排列的狀態)肯定會MLE,但是
若本次查詢為第x行,第y行,記這個學生的編號為X。
若那麼只用在最後一列操作即可。
在最後一列建一顆線段樹(大小為n+q),初始化每個位置上的數值都為1。每次找這一列的第k個位置就找第一個前面有k個1的位置(包括他自己)並將該位置數值賦值為0,這樣找到的位置的值在賦值為0前時一定為1。一開始先用一個佇列預處理出線段樹上第k個位置是什麼()。操作後都往佇列後面加入X。因為所存佇列就相當於是線段樹的一個對映,線段樹上第k個位置所對應的值就是佇列的第k個數,故答案就是將線段樹查詢到的位置的對應佇列中的位置的值。
假設n=3,m=3,q=2最後一列初始線段樹數值如下
1,1,1,1,1 ----->對應初始排列 3,6,9 --->對應所存佇列 3,6,9 --->對應真實佇列3,6,9
若第一次操作為(2,3) y==m,x=2
線上段樹上查詢並刪除
1,0,1,1,1 ----> 對應排列 3,9 ---->對應所存佇列 3,6,9 ---->對應真實佇列 3,9
再添回來,線段樹不變
對應所存佇列3,6,9,6 ----->對應真實佇列 3,9,6
若第二次操作還是(2,3),那麼這次查詢會找到線段樹上的位置為3,也就是佇列上所對應的數值9。
刪除之後
線段樹 1,0,0,1,1----> 對應所存佇列 3,6,9,6 --->對應真實佇列---->3,6
添回來,同樣線段樹不變
對應所存佇列 3,6,9,6,9 --->對應真實佇列---->3,6,9
不難看出此處的刪去不是真的刪去,而是偽刪除。
若y不等於n。
對於每行可以用線段樹(大小m+q-1)和來維護每行前m-1位置(和上述方法一樣)(注意每一行第m個數已經放入最後一列)。每次先將第x行的線段樹的查詢到的位置的刪去,將最後一列的線段樹中查找出第x個人放入第x行對應的佇列末端,將X放入最後一列的佇列的末端。此時的佇列若將前m-1個是什麼都存下來也會MLE,故我們可以利用一個性質:
記線上段樹上查詢到的位置為p。
若p<m,那麼這個人肯定不是後面加上來的人,那麼這個位置所對應的人的編號就是x*m+p。
若p>=m,那麼這個人就是後面來的第p-m+1個人。故這些人存入vector中就可以了。這些人只有q個,不會MLE。
(查詢與刪去與上述方法一模一樣,這裡就不復述了),但如果是這樣就要開n顆線段樹,空間複雜度會吃不消。故我們開動點線段樹(不懂得可以去網上先學一下)。
AC程式碼:
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
const int M=(int)3e5+5,N=(int)4e6+5;//log(n+q)*q
int n,m,q;
int tot;//線段樹們的編號
int Lson[N],Rson[N],Sum[N];//線段樹初始值為1
int Query(int L,int R,int &tid,int d) {//動點線段樹
if(tid==0)tid=++tot,Sum[tid]=R-L+1;
Sum[tid]--;
if(L==R)return L;
int mid=(L+R)>>1,Lres;
if(Lson[tid]==0)Lres=mid-L+1;
else Lres=Sum[Lson[tid]];
if(Lres>=d)return Query(L,mid,Lson[tid],d);
else return Query(mid+1,R,Rson[tid],d-Lres);
}
long long val[M<<1];
void Init() {
long long res=0;
for(int i=1; i<=n; i++)res+=m,val[i]=res;//預處理出最後一列的佇列
}
int root[M];//root[0]給最後一列的線段樹的id
vector<long long>line[M];
void solve() {
int nq=n+q,mq=m+q;
for(int i=1,id=n+1; i<=q; i++,id++) {
int x,y;
scanf("%d%d",&x,&y);
long long ans;
if(y==m) {
ans=val[Query(1,nq,root[0],x)];//在最後一列裡找到第x個人並刪去
} else {
int res=Query(1,mq,root[x],y);
if(res<m)ans=1ll*(x-1)*m+res;//若他原本就在這一行
else ans=line[x][res-m];//後面來的人
line[x].push_back(val[Query(1,nq,root[0],x)]);//從最後一列過來這行的最後一個人,並在最後一列中刪除
}
val[id]=ans;//在最後一列末尾加入這個請假的人
printf("%lld\n",ans);
}
}
int main() {
scanf("%d%d%d",&n,&m,&q);
Init();
solve();
return 0;
}