2020.04.05日常總結

P1486     [NOI2004]鬱悶的出納員\color{green}{\text{P1486\ \ \ \ \ [NOI2004]鬱悶的出納員}}

【題意】:\color{blue}{\text{【題意】:}} OIER公司 是一家大型專業化軟件公司,有着數以萬計的員工。作爲一名 出納員,我的任務之一便是統計每位員工的工資。這本來是一份不錯的工作,但是令人鬱悶的是,我們的老闆反覆無常,經常調整員工的工資。如果他心情好,就可能把每位員工的工資加上一個相同的量。反之,如果心情不好,就可能把他們的工資扣除一個相同的量。我真不知道除了調工資他還做什麼其它事情。

工資的頻繁調整很讓員工反感,尤其是集體扣除工資的時候,一旦某位員工發現自己的工資已經低於了合同規定的工資下界,他就會立刻氣憤地離開公司,並且再也不會回來了。每位員工的工資下界都是統一規定的。每當一個人離開公司,我就要從電腦中把他的工資檔案刪去,同樣,每當公司招聘了一位新員工,我就得爲他新建一個工資檔案。

老闆經常到我這邊來詢問工資情況,他並不問具體某位員工的工資情況,而是問現在工資第 kk \color{red}{\text{多}}的員工拿多少工資。每當這時,我就不得不對數萬個員工進行一次漫長的排序,然後告訴他答案。

好了,現在你已經對我的工作了解不少了。正如你猜的那樣,我想請你編一個工資統計程序。怎麼樣,不是很困難吧?

如果某個員工的初始工資低於最低工資標準,那麼將不計入最後的答案內。

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
【思路】:\color{blue}{\text{【思路】:}} 如果沒有 AS 操作,這題就是一道裸的平衡樹的題目。

由於上面兩個操作的存在,使得我們在執行它們的時候,時間複雜度會變成 O(N×logN)O(N \times \log N),其中 NN 是當前員工個數。只要出題人想卡,就是上帝來了也救不了啊!!!

怎麼辦?我們沿用 差分\color{red}{\text{差分}} 的思想,既然大家的工資是同時加,同時減,那麼我們工資之間的相對差一直不變。

也就是說,我們可以用一個變量 delta\text{delta} 表示從第 11 個操作到現在,工資一共漲了多少(delta\text{delta} 爲負代表跌)。然後執行 AS 操作時,我們只需改變 delta\text{delta} 即可。

等等,由於最低工資是一直不變的,那麼員工工資的變化不是會導致員工工資和最低工資之間的相對差發生變化嗎?

是的,所以我們在修改 delta\text{delta} 的同時,也要修改最低工資 minn\text{minn}

最後一個問題:如何刪除?如果您用其它平衡樹,可能有很厲害的方法。但是如果您一定要用 Treap 該怎麼辦呢?

既然沒有其它所謂的厲害的方法,那麼我們可以考慮直接 \color{red}{暴力} 啊!!!即直接需要刪除一個就刪除一個。

但是,這樣不會 TLE 嗎?要是一次要刪除很多很多,O(N)O(N) 個元素怎麼辦?那不是還是 TLE 到飛起嗎?

注意到刪除的前提是加入(沒有加入哪有刪除),既然加入的元素個數最多是 1×1051 \times 10^5 個,那麼刪除的數的總個數頂多不也就是 1×1051\times 10^5 個嗎?哪怕你一次就刪除 1×10511 \times 10^5 -1 個元素,總共最多也就刪除 1×1051\times 10^5 個元素,即用於刪除操作的時間最多是 O(1×105×log(1×105))O(1 \times 10^5 \times \log (1 \times 10^5)) 的,不會 TLE(除非寫炸了)。

想到這裏,基本就只剩打模板了。但是仍然有幾點要注意的:

  • 輸出的是第 kk 大(第 Nk+1N-k+1小),不是第 kk 小。
  • 輸出時不能直接輸出第 kk 大,輸出是第 kk 大元素的值 +delta+ \text{delta}

【代碼】:\color{blue}{\text{【代碼】:}}

const int N=1e5+100;
int ch[N][2],v[N],size[N];
int pri[N],cnt[N],root,tot;
inline void updata(int u){
	size[u]=size[ch[u][0]]+size[ch[u][1]]+cnt[u];
}
inline void rodata(int &u,int d){
	register int son=ch[u][d];
	ch[u][d]=ch[son][d^1];
	ch[son][d^1]=u;updata(u);
	updata(u=son);return;
}//d=0:左兒子旋爲根
 //d=1:右兒子旋爲根 
void insert(int &u,int num){
	if (u==0){
		u=(++tot);cnt[u]=size[u]=1;
		v[u]=num;pri[u]=rand();return;
	}
	++size[u];//無論如何都加一 
	if (v[u]==num){++cnt[u];return;}
	int d=num>v[u];insert(ch[u][d],num);
	if (pri[u]>pri[ch[u][d]]) rodata(u,d);
}
void Delete(int &u,int num){
	if (u==0) return;
	if (v[u]==num){
		if (cnt[u]>1){--cnt[u];--size[u];return;}
		register int d=pri[ch[u][0]]<pri[ch[u][1]];
		if (ch[u][0]==0||ch[u][1]==0) u=ch[u][0]+ch[u][1];
		else{rodata(u,d^1);Delete(u,num);return;}
	}
	else{--size[u];Delete(ch[u][v[u]<num],num);}
}
int query_rank(int u,int num){
	if (u==0) return 0;//避免題目中可能的錯誤 
	if (v[u]==num) return size[ch[u][0]]+1;
	if (v[u]>num) return query_rank(ch[u][0],num);
	return size[ch[u][0]]+cnt[u]+query_rank(ch[u][1],num);
}
int query_kth(int u,int num){
	if (num==0) return 0;//判錯,避免題目中可能的錯誤 
	if (num<=size[ch[u][0]]) return query_kth(ch[u][0],num);
	if (num>size[ch[u][0]]&&num<=size[ch[u][0]]+cnt[u]) return v[u];
	return query_kth(ch[u][1],num-size[ch[u][0]]-cnt[u]);
}
const int inf=0x7fffffff;
int prenum(int u,int num){
	if (u==0) return -inf;//避免題目中可能的錯誤 
	if (v[u]>num) return prenum(ch[u][0],num);
	return max(prenum(ch[u][1],num),v[u]);
}//查找<=num的最大的數字(前綴) 
int nxtnum(int u,int num){
	if (u==0) return inf;//避免題目中可能的錯誤 
	if (v[u]<num) return nxtnum(ch[u][1],num);
	return min(nxtnum(ch[u][0],num),v[u]);
}//查找>=num的最小的數字(後綴) 
int n,minn,delta,leave,num;
int main(){
	n=read();minn=read();
	delta=root=tot=0;//初始化 
	srand(time(NULL));//隨機數種子 
	for(int i=1,u,a1,a2;i<=n;i++){
		char opt;cin>>opt;u=read();
		switch(opt){
			case 'A':minn-=u;delta+=u;break;
			case 'F':if (num<u) printf("-1\n");//人數不夠,輸出-1 
			         else printf("%d\n",query_kth(root,num-u+1)+delta);
			         break;//注意switch-case語句必須要有break!!!
			case 'I':if (u>=minn+delta){insert(root,u-delta);num++;}
			         break;//注意初始工資>=最低工資(minn+delta)纔算入 
			case 'S':delta-=u;minn+=u;a1=minn-1;a2=0;
			         while (prenum(root,a1)!=-inf){
			             a2=a1;a1=prenum(root,a1);
			             Delete(root,prenum(root,a2));
						 --num;++leave;
					 }//刪除憤怒離開的員工 
					 break;//Don't forget it!
		}
	}
	printf("%d",leave);
	return 0;
}


本代碼沒有任何的反作弊系統(本兩句除外),請讀者放心。

read() 函數就是快讀函數,我就不給了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章