首先審題一定要清晰!!
一開始看錯題目認爲是一個任意的序列,然後枚舉d的位置繞了很大的彎子。
看到題目可以以爲是排序,這也有了第一個方法。
一、樸素枚舉
枚舉每一個包含d的序列,對序列進行排序,二分查找d的位置,若在序列中間,則累加到ans變量裏面。明顯這種算法很naive。
二、前綴和數組
認真審題以後發現,每個數無非三種可能:比d大,比d小,等於d,若一個序列的中位數爲d,那麼比d大的數一定和比d小的數個數相同。
可以標記比d大的數爲1,比d小的數位-1。這樣子就可以把問題轉化爲統計前綴和了。
一開始想到用樹狀數組。後來想起不需要支持修改操作,只需要在第一遍讀入的時候進行統計前綴和即可。
2.1 TLE操作O(n^2)
然後的思路是以d爲中點,往兩邊拓展區間,由於題目中要求區間爲奇數序列,那麼往左邊拓展奇數個時,右邊也要拓展奇數個,枚舉區間判斷左邊的和+右邊的和是否爲0。
for(int i=num;i>0;i--)
for(int j=((num-i+1)&1)?num:num+1;j<=n;j+=2)
if(sum[j]-sum[i-1]==0)
ans++;
好吧枚舉區間是真的慢。
2.2預處理優化時間O(n)
注意到左邊的數等於右邊的數,因爲左邊的數一定,只需要統計右邊有多少個區間的數和左邊的區間是互爲相反數的即可。
預處理出右區間每一個數值的個數(桶排!),每次得到左區間的數值,只需要在O(1)的時間裏查詢右區間的個數即可。
注意坑:若左區間的數值爲0,那麼不需要右區間也可以組成一箇中位數區間,此時要直接統計進答案。同理右區間和中位數的單區間也一樣。
for(int i=num+1;i<=n;i+=2){
r1[a[i]-a[num]+K]++;
if(a[i]-a[num]==0)ans++;
}
for(int i=num+2;i<=n;i+=2){
r2[a[i]-a[num]+K]++;
if(a[i]-a[num]==0)ans++;
}
//for(int i=num-1;i>0;i--)l[a[num-1]-a[i]+K]++;
//cout<<r[K-1]<<endl;
for(int i=1;i<num;i++){
if(a[num-1]-a[i-1]==0)ans++;
if((num-i+1)&1)
ans+=r2[K-(a[num-1]-a[i-1])];
else ans+=r1[K-(a[num-1]-a[i-1])];
}
cout<<(ans+1)<<endl;]++;
最後一點小優化:不需要分成兩個數組儲存,由於除了中間的數外只有1和-1,如果左區間長度爲奇數,那麼它的值也是奇數,右區間只有奇數長度的能和它對應,那麼就不需要考慮奇偶數分開存放了。
#include <iostream>
#include <cstdio>
using namespace std;
const int K=99999/2;
int n,b,num=0,ans=0;
int a[100009],r1[100009],r2[100009];
void read(int k){
int tmp=0;
char ch=getchar();
while(ch>'9'||ch<'0')ch=getchar();
while(ch>='0'&&ch<='9'){tmp=tmp*10+ch-'0';ch=getchar();}
if(tmp>b)a[k]=a[k-1]+1;
else if(tmp<b)a[k]=a[k-1]-1;
else a[k]=a[k-1];
if(tmp==b)num=k;
return;
}
int main()
{
scanf("%d%d",&n,&b);
for(int i=1;i<=n;i++)read(i);
for(int i=num+1;i<=n;i+=2){
r1[a[i]-a[num]+K]++;
if(a[i]-a[num]==0)ans++;
}
for(int i=num+2;i<=n;i+=2){
r2[a[i]-a[num]+K]++;
if(a[i]-a[num]==0)ans++;
}
//for(int i=num-1;i>0;i--)l[a[num-1]-a[i]+K]++;
//cout<<r[K-1]<<endl;
for(int i=1;i<num;i++){
if(a[num-1]-a[i-1]==0)ans++;
if((num-i+1)&1)
ans+=r2[K-(a[num-1]-a[i-1])];
else ans+=r1[K-(a[num-1]-a[i-1])];
}
cout<<(ans+1)<<endl;
return 0;
}