P1627 [CQOI2009]中位數 題解

博客園同步

原題鏈接

簡要題意:

給定一個 11 ~ nn 的排列,求以 bb 爲中位數的 連續子序列且長度爲奇數 的個數。

顯然這段序列包含 bb.

中位數的定義:排序後在最中間的數。

算法一

對於 30%30 \% 的數據,n100n \leq 100.

由於這段序列一定包含 bb,那麼我們可以枚舉區間 [i,j][i,j] 包含 bb(有類似於雙指針),然後單獨取出 [i,j][i,j] 這段進行排序,暴力判斷即可。

時間複雜度:O(n3logn)O(n^3 \log n).

實際得分:30pts30pts.

算法二

對於 60%60 \% 的數據,n1000n \leq 1000.

顯然我們不需要每次都把 [i,j][i,j] 這段取出,可以一次次擴展。

比方說枚舉 ii (從 dd11ddbb 的位置),然後枚舉 jjddnn. 對於每個 jj,只需在原來數組的基礎上添上一個 aja_j 即可;如果 j=nj=n 的話,添加之後要把數組清空。

那麼每次只需要插入一個數的話,我們可以用 插入排序,因爲已經排序的是有序的,因爲插入的位置可以用二分算出。插入操作我們不用數組維護,用 vector\text{vector} 維護會方便很多。

時間複雜度:O(n2logn)O(n^2 \log n).

實際得分:60pts60pts.

算法三

對於 60%60 \% 的數據,n1000n \leq 1000.

拋開排序過程,我們想:因爲排列的性質,不存在重複數。所以,一個連續序列的中位數爲 bb 當且僅當比 bb 大的數的個數和比 bb 小的數的個數相等。 那麼,對於 dd 的左邊,線性 dp\text{dp} ,用 fif_i 表示 ii ~ ddii 大的數的個數,gig_i 是小的,同理 dd 的右邊也推一遍。

那麼,我們枚舉左右端點 i,ji,j 只需要 O(1)O(1) 判斷即可。即 fi+gi=fj+gjf_i + g_i = f_j + g_j.

時間複雜度:O(n2)O(n^2).

實際得分:60pts60pts.

算法四

對於 60%60 \% 的數據,n1000n \leq 1000.

從算法三的 dp\text{dp} 上入手,我們發現,fi+gi=fj+gjf_i + g_i = f_j + g_j 等價於 fifj=gigjf_i - f_j = g_i - g_j. 所以我們只需要算出 bb 大的數的個數與比 bb 小的數的個數之差 重新作爲 ff 數組的狀態,然後枚舉端點即可。

時間複雜度:O(n2)O(n^2).

實際得分:60pts60pts.

算法五

對於 100%100 \% 的數據,n105n \leq 10^5.

從算法四上再優化一下,其實對於固定的一個左端點 ii,我們只需要算出有多少個 jdj \geq dfi=fjf_i = f_j 即可。

也就是說,我們需要維護 區間查詢相等個數

智商不夠,數據結構來湊。所以這個查詢我們可以用 Treap\text{Treap} 或者 Splay\text{Splay} 來實現。(隨便用個平衡樹板子都能實現的)

時間複雜度:O(nlogn)O(n \log n).

實際得分:100pts100pts.

算法六

對於 100%100 \% 的數據,n105n \leq 10^5.

從算法五上入手,你發現 區間查詢相等個數 是靜態查詢,不需要修改。那你可以用 權值線段樹(主席樹) 解決本題。

時間複雜度:O(nlogn)O(n \log n).

實際得分:100pts100pts.

算法七

對於 100%100 \% 的數據,n105n \leq 10^5.

在算法六的基礎上,你發現不僅是靜態,而且是固定的一個區間 [d,n][d,n]. 那麼我們不需要用 權值線段樹(可以理解爲 nn 棵線段樹),只需要 11 棵線段樹即可。

時間複雜度:O(nlogn)O(n \log n).

實際得分:100pts100pts.

算法八

對於 100%100 \% 的數據,n105n \leq 10^5.

如果你的程序在算法七止步不前,只能說明你是個天才,離最後的成功只差幾步了。

固定區間維護靜態相等個數,聽上去很高大上啊,其實不就是個 map\text{map} 嗎?

某同學:我還能 %$#^%*(%&))

嗯,改成 map\text{map} 之後感覺簡單多了,是不是?然後我們直接省去 ff 數組,直接存入 map\text{map}.

時間複雜度:O(nlogn)O(n \log n).

實際得分:100pts100pts.

//爲了看起來清晰 , 用了嵌套三目運算符
// q[tot+=((a[i]>b)?1:(a[i]<b)?-1:0)]++; 其實相當於這幾句:
// if(a[i]>b) tot++; 
// if(a[i]<b) tot--;
// q[tot]++;
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int N=1e5+1;

inline int read(){char ch=getchar();int f=1;while(ch<'0' || ch>'9') {if(ch=='-') f=-f; ch=getchar();}
	int x=0;while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();return x*f;}

int n,b,wz,a[N];
int tot,sum; ll ans=0;
map<int,int> q;

int main(){
	n=read(),b=read();
	for(int i=1;i<=n;i++) a[i]=read(),wz=(a[i]==b)?i:wz;
	for(int i=wz;i<=n;i++) q[tot+=((a[i]>b)?1:(a[i]<b)?-1:0)]++;
	for(int i=wz;i>=1;i--) ans+=q[0-(sum+=(a[i]>b)?1:((a[i]<b)?-1:0))];
	printf("%lld\n",ans);
	return 0;
}

算法九

對於 200%200 \% 的數據,n107n \leq 10^7.

(實際上是本人的一個加強)

算法八的基礎上,我們可以嘗試拿掉這個 log\log.

但是你很快發現下標雖然不超過 10710^7,但是會有負數,可能有 107-10^7.

顯然,哈希處理負下標 是這題的最終正解。

把每個下標都加上 nn,解決負下標之後 O(1)O(1) 查詢,拿掉 map\text{map} 還解決了 log\log.

時間複雜度:O(n)O(n).

實際得分:200pts200pts.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章