博客:
先回想一下启发式合并求序列属性的方法:以属性为序列满足性质的数对个数举例。现在有两个集合,要求出合并后的数对个数。我们枚举更小集合中的元素,在另一个集合中查询数对个数。查询完之后放入更大的元素。时间复杂度 O ( n log n ) O(n\log n) O(nlogn)
启发式分治其实就是启发式合并的逆过程。我们选择一个区间中的某个下标作为分界点(分治求逆序对、排序算法中选择的分界点都是中间点),统计左右两子区间的数对个数。然后我们需要计算位于分界点两端的数对个数了,这里就需要启发式分治的核心:枚举区间元素更少的元素,然后在另一个区间内统计数对的个数。这刚好就是启发式合并的逆过程。这保证了时间复杂度同样是 O ( n log n ) O(n\log n) O(nlogn)
#613. 好序列
题意:有一个长为 n n n 的序列 A 1 , A 2 , ⋯ , A n A_1,A_2,\cdots,A_n A1,A2,⋯,An 。定义一个序列 { A } \{A\} {A} 是好的, 当且仅当他的每一个子区间 [ l , r ] [l,r] [l,r] 满足,至少存在一个元素 x x x 仅出现了一次。
题解:- (暴力/启发式分裂)代码源每日一题 Div1 好序列
思路:先预处理每个数和他相同且最近的数的下标。我们对于一个区间 [ l , r ] [l, r] [l,r] ,如果存在一个只出现一次的数字,那么横跨这个数字的子区间一定是好的,不用考虑,只需考虑分割出来的两个子区间。这里的核心是需要从两端向中间枚举,这样可以保证枚举的次数一定小于总元素个数的二分之一。
AC代码:http://oj.daimayuan.top/submission/299119
HDU 2019 多校 Make Rounddog Happy
题意:问长度为 n ( n ≤ 3 × 1 0 5 ) n(n\leq 3\times 10^5) n(n≤3×105) 有多少个子区间满足 max ( a l , a l + 1 , ⋯ , a r ) − ( r − l + 1 ) ≤ k \max(a_l,a_{l+1},\cdots,a_r)-(r-l+1) \leq k max(al,al+1,⋯,ar)−(r−l+1)≤k 且区间内元素互不相同
题解:启发式分治
思路:启发式分治以最大值下标为分界点。计算数对两端位于分界点的异端,则枚举长度更小的区间内的元素,计算即可。
AC代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
#define endl '\n'
#define fi first
#define se second
#define ppb pop_back
#define pb push_back
#define ios ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define all(x) x.begin(), x.end()
#define rall(x) x.rbegin(), x.rend()
#define mset(x, a) memset(x, a, sizeof (x))
#define rep(i, l, r) for(LL i = l; i <= (r); ++ i)
#define per(i, r, l) for(LL i = r; i >= (l); -- i)
#define reps(i, l, r, d) for(LL i = l; i <= (r); i += d)
#define pers(i, r, l, d) for(LL i = r; i >= (l); i -= d)
template<class T> bool ckmax(T& a, T b) { return a < b ? (a = b, 1) : 0; }
template<class T> bool ckmin(T& a, T b) { return a > b ? (a = b, 1) : 0; }
const int N = 3e5 + 10, M = 1e5 + 10, LG = 20;
const LL p = 1e9 + 7;
struct st_pair{
int w, idx;
bool operator < (const st_pair & x) const{
return w < x.w;
}
};
st_pair f[N][LG];
int n, a[N], k, cnt[N], L[N], R[N];
void init(){
rep(j, 0, LG - 1){
for(int i = 1; i + (1 << j) - 1 <= n; i ++ ){
if(!j) f[i][j] = {a[i], i};
else f[i][j] = max(f[i][j - 1], f[i + (1 << j - 1)][j - 1]);
}
}
}
st_pair query(int l, int r){
int k = __lg(r - l + 1);
return max(f[l][k], f[r - (1 << k) + 1][k]);
}
LL split(int l, int r){
if(l > r) return 0;
if(l == r) return a[l] - 1 <= k;
st_pair pr = query(l, r);
LL mid = pr.idx, mx = pr.w;
LL res = split(l, mid - 1) + split(mid + 1, r);
if(mid - l < r - mid){
rep(i, l, mid) if(R[i] >= mid) res += max(0LL, min(R[i], r) - max(mid, mx - k + i - 1) + 1);
}else{
rep(i, mid, r) if(L[i] <= mid) res += max(0LL, min(mid, i + 1 - mx + k) - max(L[i], l) + 1);
}
return res;
}
void solve()
{
cin >> n >> k;
rep(i, 1, n) cin >> a[i];
init();
rep(i, 1, n) cnt[i] = 0;
for(int i = 1, j = 0; i <= n; i ++ ){
while(j < n && !cnt[a[j + 1]]) j ++ , cnt[a[j]] ++ ;
R[i] = j;
cnt[a[i]] -- ;
}
rep(i, 1, n) cnt[i] = 0;
for(int i = n, j = n + 1; i >= 1; i -- ){
while(j > 1 && !cnt[a[j - 1]]) j -- , cnt[a[j]] ++ ;
L[i] = j;
cnt[a[i]] -- ;
}
cout << split(1, n) << endl;
}
int main()
{
ios;
int T; cin >> T;
while(T -- )
solve();
return 0;
}