2021暑假 HDU中超 第五場 1009
被這個題折磨了好幾天
從比賽中的奇怪思路繞到洛谷原題,從分治繞到樹狀陣列,還先去搞了半天模板題,可謂是曲線救國
2021暑假 HDU中超 第五場 1009
Array
Time Limit: 15000/8000 MS (Java/Others) Memory Limit: 524288/524288 K (Java/Others)
Total Submission(s): 235 Accepted Submission(s): 48
題意
給你一個序列\(a[1...n]\),求存在絕對眾數的子區間個數。
絕對眾數指:區間中出現次數最多的那個數,出現次數嚴格大於區間長度的一半。
Sample Input
1 10 3303 70463 3303 3303 3303 70463 3303 3303 70463 70463
Sample Output
47
考場思路:
玄學而虛假的主席樹亂搞做法
曲線救國:
首先注意到這是洛谷原題(RNM退錢)
分治做法:(只能過洛谷過不了比賽題)
對於區間 \([l,r]\),中間值 \(mid\), 我們需要統計經過 \(mid\) 的所有合法區間 \([l', r']\)
而如果 \([l',r']\) 擁有絕對眾數,那麼 \([l', mid]\) 和 \([mid+1, r']\) 中至少有一個有絕對眾數
那麼可以從 \(mid\) 往兩邊搜,統計每種數出現的次數,如果搜到某個位置時,當前數出現次數大於區間長度的一半,則這個數有可能成為絕對眾數。我們首先 \(O(r-l)\)
再依次列舉這些眾數,對於眾數 \(X\),我們可以按投票的思想,把所有的 \(X\) 記為1,其他數記為-1,計算字首和,這樣可以判斷任意區間是否滿足條件。
若區間 \([l',r']\) 滿足 \(sum[r'] - sum[l'-1] > 0\),則這個區間有絕對眾數 \(X\)
也就是說需要在 \(mid\) 左右找到滿足 \(sum[r'] > sum[l'-1]\) 的點對個數
至此問題轉化為了一個比較經典的模型,用樹狀陣列搞即可。
程式碼複雜度 \(O(nlog^3n)\) 洛谷能過
但這題直接從1e5加強到1e6,直接T飛
AC做法:
首先記錄每一種數字出現的位置,然後列舉每一種數字,設當前列舉到 \(X\)
然後還是按投票的思想,把所有的 \(X\) 記為1,其他數記為-1,計算字首和 \(sum\)
若區間 \([l',r']\) 滿足 \(sum[r'] - sum[l'-1] > 0\),則這個區間有絕對眾數 \(X\)(和上面做法一樣)
考慮從左到右遍歷,對於當前位置的字首和 \(sum\) ,我們需要求出它左邊比它小的字首和個數
記錄每個字首和出現的次數為 \(cnt[x]\),用樹狀陣列維護和查詢
複雜度大概是 \(O(n^2logn)\)
想到我們記錄了每個數字出現的位置,那麼是不是可以在遍歷的時候直接跳過一串連續的-1?
跳的時候面臨兩個問題:
- 如何把這一串-1位置上的字首和計入 \(cnt\) 陣列
- 如何計算這些-1位置對答案的貢獻
對於問題二:
首先回憶一下:位置 \(p\) 的貢獻,就是 \(p\) 前面比 \(sum[p]\) 小的字首和個數
也就是 \(cnt\) 陣列從最小值加到 \(sum[p]\) 為止的字首和,設這個字首和為 \(ssum\)
\(ssum[p]\) 是位置 \(p\) 上的答案
而我們需要知道這一連串-1的答案之和
顯然再搞一遍字首和就行了。。。設 \(sssum\) 是這個東西。。。
現在問題被轉化成了:我們要維護 \(cnt\) 陣列的字首和的字首和
別急 還沒完
對於問題一,其實很簡單,對 \(cnt\) 陣列搞個差分陣列 \(d\) 就好了,做到 \(O(1)\) 插入
那麼只需要維護 \(d\) 陣列的字首和的字首和的字首和。。。
其實寫起來沒那麼麻煩
我們只需要一個支援單點修改,三階字首和查詢的資料結構
還是樹狀陣列!
打包起來會比較好寫一點
struct BitTree{//4行普通樹狀陣列
ll t[2*maxn];
int lb(int x) {return x&-x;}
void add(int p, ll ad){for(int i=p;i<=2*n+20;i+=lb(i)) t[i]+=ad;}
ll sum(int p){ return p?t[p] + sum(p-lb(p)):0ll;}
};
struct BitTree3{//三階字首和的樹狀陣列
BitTree bt1, bt2, bt3;
void add(int p, ll ad){
bt1.add(p, ad);
bt2.add(p, ad * p);
bt3.add(p, ad * p * p);
}
ll sum(ll p){ return ((p+1)*(p+2) * bt1.sum(p) - (2*p+3) * bt2.sum(p) + bt3.sum(p)) / 2;}
void modify(int l, int r, int dt = 1){
add(l + bs, dt);
add(r + 1 + bs, -dt);
}
ll query(int l, int r){
if(l > r) return 0;
return sum(r - 1 + bs) - sum(l - 2 + bs);
}
};
AC程式碼:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#ifdef ACM_LOCAL
const int maxn = 1000040;
#else
const int maxn = 1000040;
#endif
int n; ll bs;
vector<int> pos[maxn];
struct BitTree{//4行樹狀陣列
ll t[2*maxn];
int lb(int x) {return x&-x;}
void add(int p, ll ad){for(int i=p;i<=2*n+20;i+=lb(i)) t[i]+=ad;}
ll sum(int p){ return p?t[p] + sum(p-lb(p)):0ll;}
};
struct BitTree3{//三階字首和的樹狀陣列
BitTree bt1, bt2, bt3;
void add(int p, ll ad){
bt1.add(p, ad);
bt2.add(p, ad * p);
bt3.add(p, ad * p * p);
}
ll sum(ll p){ return ((p+1)*(p+2) * bt1.sum(p) - (2*p+3) * bt2.sum(p) + bt3.sum(p)) / 2;}
void modify(int l, int r, int dt = 1){
add(l + bs, dt);
add(r + 1 + bs, -dt);
}
ll query(int l, int r){
if(l > r) return 0;
return sum(r - 1 + bs) - sum(l - 2 + bs);
}
};
BitTree3 t;
void solve(){
cin >> n >> bs;
for(int i=1;i<=n;i++){
int xx;
cin >> xx;
pos[xx].push_back(i);
}
ll ans = 0;
bs = n + 10;
for(int i=0;i<=1e6;i++){
if(!pos[i].size()) continue;
ll sum = 2 - pos[i][0];
t.modify(1-pos[i][0], 0);
t.modify(sum, sum);
ans++;
for(int j=1;j<pos[i].size();j++){
int l = pos[i][j] - pos[i][j-1] - 1;
ans += t.query(sum - l, sum - 1);
t.modify(sum - l, sum - 1);
(sum -= l) += 1;
ans += t.query(sum, sum);
t.modify(sum, sum);
}
int l = n + 1 - pos[i][pos[i].size()-1] - 1;
ans += t.query(sum - l, sum - 1);
//相同操作撤銷
t.modify(1-pos[i][0], 0, -1);
sum = 2 - pos[i][0];
t.modify(sum, sum, -1);
for(int j=1;j<pos[i].size();j++){
int l = pos[i][j] - pos[i][j-1] - 1;
t.modify(sum - l, sum - 1, -1);
(sum -= l) += 1;
t.modify(sum, sum, -1);
}
pos[i].clear();
}
cout << ans << '\n';
}