【启发式分治】启发式合并的逆思想

博客:

先回想一下启发式合并求序列属性的方法:以属性为序列满足性质的数对个数举例。现在有两个集合,要求出合并后的数对个数。我们枚举更小集合中的元素,在另一个集合中查询数对个数。查询完之后放入更大的元素。时间复杂度 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(n3×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)(rl+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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值