題意:
給一個數組a,求區間內眾數的數字個數大於區間長度一半的區間個數。
如眾數是ai,ai個數>(r-l+1)/2符合題意。
思路:
因為我們每次只要看眾數,所以可以根據出現的每個數來做。
對於一個數cnt[i],如果他在原串里的這個位置出現了,那就讓這個位置為1,否則為-1。
那么對於這個東西我們就可以求前綴和,如果sum[i]-sum[j]>0,說明這段區間里的這個數比其他數總和多。
所以就變成了對於上面定義的這樣一個數組,求前綴和sum[i]>sum[j]的個數(i>j)
定義兩個數組f1[sum]為前綴和為sum的點的個數。
f2[sum]為差分數組來維護f1。
對於1我們只需要線性掃描+維護就行了。
特殊情況是對於前綴和為當前最小時的連續-1,因為這個時候不會產生新解,所以可以利用剛才的f2數組來做差分,快速實現對於f1某一段的+1
注意點:這里初始時有一個前綴和為0,但是不能直接用進去,因為后續如果到過負數,那么開始i=0時的0是不能被計算進后面的答案里的。
因此維護幾個值的時候要前移一下,先計算這個點之前上一次的信息,來內含i=0時的信息。
下附代碼:
1 #include<bits/stdc++.h> 2 #define ll long long 3 using namespace std; 4 vector<ll> cnt; 5 vector<ll> G[1000005]; 6 ll res,n,a[1000005]; 7 ll flag[1000005]; 8 ll find(ll x){ 9 ll ans=0; 10 ll sum=0,minn=0; 11 unordered_map<ll,ll> f1,f2; 12 G[x].push_back(n+1); 13 ll k=0; 14 for (ll i=1; i<=n; i++){ 15 if (i>G[x][k]) k++; 16 if (a[i]!=x && sum==minn){ 17 ll len=G[x][k]-i-1; 18 f2[sum+1]--; 19 f2[sum-len]++; 20 i=G[x][k]-1; 21 sum=sum-len-1; 22 } 23 else if (a[i]==x) { 24 f1[sum]=f1[sum]+f2[sum]; 25 f2[sum+1]+=f2[sum]; 26 f2[sum]=0; 27 f1[sum]++; 28 ans+=f1[sum]; 29 res+=ans; 30 sum++; 31 32 } 33 else { 34 f1[sum]++; 35 sum--; 36 ans-=f1[sum]; 37 res+=ans; 38 } 39 if (sum<minn) minn=sum; 40 } 41 } 42 int main(){ 43 ll T; 44 scanf("%lld",&T); 45 while (T--){ 46 scanf("%d",&n); 47 cnt.clear(); 48 memset(flag,0,sizeof(flag)); 49 for (ll i=1; i<=n; i++){ 50 scanf("%lld",&a[i]); 51 if (!flag[a[i]]) cnt.push_back(a[i]),flag[a[i]]=1; 52 G[a[i]].push_back(i); 53 } 54 res=0; 55 for (auto i:cnt){ 56 find(i); 57 G[i].clear(); 58 } 59 printf("%lld\n",res); 60 } 61 }
賽場上看到ai的范圍,有因為題目性質提煉出來是看眾數的個數,所以其實只要看一個數就行了。
自然而然想到能不能枚舉每一個出現的ai,然后快速維護出個數大於一半的區間數。
當時時間復雜度搞錯了,感覺前綴和會T,但是其實這是個只有1和-1的前綴和,可以通過差分數組跳過一段-1來優化。
下次抓到靈感還是要多挖掘一下,不能輕易否決
