1. 程式人生 > >NOIP2017 列隊 動點線段樹

NOIP2017 列隊 動點線段樹

題目連結:列隊

主要思路:

和前面x==1的思路有些相似。

首先,若用陣列存地圖(即排列的狀態)肯定會MLE,但是

若本次查詢為第x行,第y行,記這個學生的編號為X。

y==m那麼只用在最後一列操作即可。

在最後一列建一顆線段樹(大小為n+q),初始化每個位置上的數值都為1。每次找這一列的第k個位置就找第一個前面有k個1的位置(包括他自己)並將該位置數值賦值為0,這樣找到的位置的值在賦值為0前時一定為1。一開始先用一個佇列預處理出線段樹上第k個位置是什麼(1<=k<=n)。操作後都往佇列後面加入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;
}