1. 程式人生 > >NOIP2017列隊

NOIP2017列隊

Sylvia 是一個熱愛學習的女♂孩子。
前段時間,Sylvia 參加了學校的軍訓。眾所周知,軍訓的時候需要站方陣。
Sylvia 所在的方陣中有名學生,方陣的行數為 n,列數為 m。
為了便於管理,教官在訓練開始時,按照從前到後,從左到右的順序給方陣中 的學生從 1 到 編上了號碼(參見後面的樣例)。即:初始時,第 i行第 j列 的學生的編號是。
然而在練習方陣的時候,經常會有學生因為各種各樣的事情需要離隊。在一天 中,一共發生了 q q件這樣的離隊事件。每一次離隊事件可以用數對(x,y) (1 \le x \le n, 1 \le y \le m)描述,表示第 x行第 y列的學生離隊。
在有學生離隊後,隊伍中出現了一個空位。為了隊伍的整齊,教官會依次下達 這樣的兩條指令:
向左看齊。這時第一列保持不動,所有學生向左填補空缺。不難發現在這條 指令之後,空位在第 x 行第 m列。
向前看齊。這時第一行保持不動,所有學生向前填補空缺。不難發現在這條 指令之後,空位在第 n行第 m 列。
教官規定不能有兩個或更多學生同時離隊。即在前一個離隊的學生歸隊之後, 下一個學生才能離隊。因此在每一個離隊的學生要歸隊時,隊伍中有且僅有第 n 行 第 m 列一個空位,這時這個學生會自然地填補到這個位置。
因為站方陣真的很無聊,所以 Sylvia 想要計算每一次離隊事件中,離隊的同學 的編號是多少。
注意:每一個同學的編號不會隨著離隊事件的發生而改變,在發生離隊事件後 方陣中同學的編號可能是亂序的。
輸入
輸入共 q+1行。
第 1 行包含 3 個用空格分隔的正整數 n, m, q,表示方陣大小是 n 行 m 列,一共發 生了 q 次事件。
接下來 q行按照事件發生順序描述了 q 件事件。每一行是兩個整數 x, y用一個空 格分隔,表示這個離隊事件中離隊的學生當時排在第 x行第 y列。
輸出
按照事件輸入的順序,每一個事件輸出一行一個整數,表示這個離隊事件中離隊學 生的編號。
在這裡插入圖片描述

這個題首先是動態開點線段樹的模板題。
動態開點線段樹用來維護有幾個人被刪掉,快速求出當前這行(列)的第k個人是誰。
然後就很板了啊;
AC Code(寫了70行算很菜的了):

#include<cstdio>
#include<cstring>
#include<cctype>
#include<algorithm>
#include<vector>
#define maxn 300005
#define maxm 300005*20
#define LL long long
using namespace std;

int n,m,q,len;

int tot,lc[maxm],rc[maxm],cut[maxm];
int rt[maxn];
vector<LL>item[maxn];

void get(int &res){
	char ch;
	while(!isdigit(ch=getchar()));
	for(res=ch-'0';isdigit(ch=getchar());res=res*10+ch-'0');
}

int New()
{
	++tot;
	cut[tot]=lc[tot]=rc[tot]=0;
	return tot;
}

void Insert(int &now,int l,int r,int pos)
{
	if(!now)now=New();
	cut[now]++;
	if(l==r) return;
	int mid=(l+r)>>1;
	if(pos<=mid) Insert(lc[now],l,mid,pos);
	else Insert(rc[now],mid+1,r,pos);
}

int Query(int now,int l,int r,int pos)
{
	if(l==r) return l;
	int mid=(l+r)>>1,siz=mid-l+1-cut[lc[now]];
	if(siz>=pos) return Query(lc[now],l,mid,pos);
	else return Query(rc[now],mid+1,r,pos-siz);
}

LL Delrow(int u,LL suc)
{
	int tmp;
	Insert(rt[n+1],1,len,tmp=Query(rt[n+1],1,len,u));
	LL ret=(tmp<=n ? 1ll*tmp*m:item[n+1][tmp-n-1]);
	item[n+1].push_back(suc==-1?ret:suc);
	return ret;
}

LL Delcol(int u,int v)
{
	int tmp;
	Insert(rt[u],1,len,tmp=Query(rt[u],1,len,v));
	LL ret=(tmp<m ? 1ll*(u-1)*m+tmp:item[u][tmp-m]);
	item[u].push_back(Delrow(u,ret));
	return ret;
}

int main(){
	
	//freopen("1.in","r",stdin);
	//freopen("1.out","w",stdout);
	
	int u,v;
	get(n),get(m),get(q);len=max(n+q,m+q);	
	for(int i=1;i<=q;i++)
	{
		get(u),get(v);
 		printf("%lld\n",(v==m)?Delrow(u,-1):Delcol(u,v));
	}
}

但是出題人揚言要卡線段樹,放樹狀陣列過。
但樹狀陣列做這個題就有點曲折了。
第一個難點,線段樹的二分結構使得我們可以邊二分邊求字首和,做到複雜度O(logn),但是樹狀陣列在蒟蒻的眼中一般來說都只能二分套樹狀陣列O(log^2n),這複雜度首先就不對了。
樹狀陣列也是可以O(logn)並且常數更小的完成這個任務的。
如果你學習過zkw線段樹,你可以發現樹狀陣列就是一個省了一半空間的線段樹加上中序遍歷。
實際上還是可以實現的,具體看程式碼。
第二個難點,樹狀陣列不能動態開點。
但是你會發現,其實我們需要維護的資訊,因為我們是惰性刪除,我們只需要求實際的位置就可以,而這針對於各行各列都是相對獨立的,那麼完全沒有必要一遍求出答案,每一行分別處理,最後利用最後一列求出答案就行。

AC Code(50行尚可):

#define maxn 300005
#define LL long long
using namespace std;

char cb[1<<15],*cs,*ct;
#define getc() (cs==ct&&(ct=(cs=cb)+fread(cb,1,1<<15,stdin),cs==ct)?0:*cs++)
inline void read(int &res){ char ch;for(;!isdigit(ch=getc()););for(res=ch-'0';isdigit(ch=getc());res=res*10+ch-'0'); }

int n,m,q;
int siz,tr[maxn*4],x[maxn],y[maxn],ry[maxn];
vector<LL>vec[maxn],qry[maxn];
inline void upd(int now,int val){ for(;now<=siz;now+=now&-now) tr[now]+=val; }
inline int qsum(int now){ int ret = 0;for(;now;now-=now&-now) ret+=tr[now];return ret; }
inline int Find(int x)
{
	int ret=1,sum=0;
	for(;;ret<<=1) if((ret<<1)-tr[ret<<1]>=x) break;
	if(ret-tr[ret] == x) return ret;
	sum = tr[ret];
	for(int k=ret>>1;k;k>>=1) if((ret+k)-tr[ret+k]-sum<x) ret+=k,sum+=tr[ret];
	return ret+1;
}

int main()
{
	read(n),read(m),read(q),siz=max(n,m)+q;
	for(int i=1;i<=q;i++)
	{
		read(x[i]),read(y[i]);
		if(y[i]!=m) qry[x[i]].push_back(i);
	}
	for(int i=1,sz;i<=n;i++){
		sz=qry[i].size();
		for(int j=0;j<sz;j++)
			upd(ry[qry[i][j]]=Find(y[qry[i][j]]),1);
		for(int j=0;j<sz;j++)
			upd(ry[qry[i][j]],-1);
	}
	for(int i=1,row;i<=q;i++)
	{
		LL ans = 0;
		row=Find(x[i]),upd(row,1);
		ans=(row <=n ? 1ll * row * m : vec[n+1][row-n-1]);
		if(y[i]!=m)
			vec[x[i]].push_back(ans),ans=(ry[i] <= m-1 ? 1ll * (x[i]-1)*m+ry[i] : vec[x[i]][ry[i]-m]);
		vec[n+1].push_back(ans);
		printf("%lld\n",ans);
	}
}