HDU - 6701 Make Rounddog Happy(启发式分治)

Description

题目大意是求一数列\(\{a_i\}\)中有多少区间\([l, r]\)满足\(max(a_l,a_{l+1},…,a_r)−(r−l+1)≤k\),且\(a_l,a_{l+1},…,a_r\)各不相同。

思路

一开始的想法是计算每个点的贡献。例如\(a_i\)为最大值时,先预处理出它覆盖的最大范围,然后统计其中满足条件的区间有多少。但由于一开始不好判断要处理每个数的区间会不会超时,所以最后没写出来。

这里解法是从整个区间开始分治处理。找出当前区间最大值的位置,然后计算包含最大值满足条件的区间有多少,累加起来即可。在计数时,从短边开始遍历,这样才能保证复杂度是nlogn级别。处理完当前区间,再递归处理左右区间。细节具体见代码。

后来发现递归分治和计算每个点的贡献的本质是一样的。

总结一下,启发式分治就是可以处理区间中有关最大最小值的子区间数或者点对数的问题。高效的关键是它每次分治前的处理只处理短边。

注意事项

本题需要求区间最值,所以我使用了st表,暴露了我st表板子的漏洞。
原本用的数组是st[1e5][50],会导致tle。需要改成st[50][1e5]时间才会正常。
根据学长的说法,这里的原因似乎和cache和分支预测有关。

#include <iostream>
#include <cstdio>
#include <queue>
#include <algorithm>
#include <map>
#include <set>
#include <vector>
#include <cstring>
#include <string>
#include <deque>
#include <cmath>
#include <iomanip>
#include <cctype>

#define endl '\n'
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define FILE freopen("..//data_generator//in.txt","r",stdin),freopen("res.txt","w",stdout)
#define FI freopen("..//data_generator//in.txt","r",stdin)
#define FO freopen("res.txt","w",stdout)
#define pb push_back
#define mp make_pair
#define seteps(N) fixed << setprecision(N)

typedef unsigned long long ull;
typedef long long ll;
using namespace std;


/*-----------------------------------------------------------------*/

#define INF 0x3f3f3f3f

const int N = 3e5 + 10;
const double eps = 1e5;
int n, k;
int arr[60][N];  //st表,但里面存的是最大值位置而不是最大值
int ldif[N];    //左边连续不相同的边界
int rdif[N];    //右边连续不相同的边界
int num[N];     //数列
bool vis[N];    //用于计算ldif和rdif
int lg2[N];
ll ans;

int q(int l, int r) {
    int k = lg2[r - l + 1];
    return num[arr[k][l]] >= num[arr[k][r-(1<<k)+1]] ? arr[k][l] : arr[k][r-(1<<k)+1];
}

void init(int n) {
    for(int k = 1; (1<<k) <= n; k++) {
        for(int i=1; i+(1<<k)-1<=n; i++) {
            arr[k][i] = num[arr[k-1][i]] >= num[arr[k-1][i+(1<<(k-1))]] ? arr[k-1][i] : arr[k-1][i+(1<<(k-1))]; //arr存最大值位置
        }
    }
}

void dfs(int l, int r) {
    if(l > r) return ;
    int p = q(l, r);    //当前区间最大值的位置
    int ml = max(ldif[p], l);
    int mr = min(rdif[p], r);
    if(p - ml <= mr - p) {  //取较短的边
        for(int j = ml; j <= p; j++) {
            int len = num[p] - k;
            int L = max(j + len - 1, p);
            int R = min(rdif[j], mr);
            ans += max(R - L + 1, 0);
        }
    } else {
        for(int j = p; j <= mr; j++) {
            int len = num[p] - k;
            int L = max(ldif[j], ml);
            int R = min(j - len + 1, p);
            ans += max(R - L + 1, 0);
        }
    }
    dfs(l, p - 1);
    dfs(p + 1, r);
}

int main() {
    IOS;
    lg2[0] = -1;
    for(int i = 1; i < N; i++) {
        lg2[i] = lg2[i >> 1] + 1;
        arr[0][i] = i;
    }
    int t;
    cin >> t;
    while(t--) {
        cin >> n >> k;
        for(int i = 1; i <= n; i++) {
            cin >> num[i];
        }
        init(n);
        int l = 1, r = 1;
        while(l <= n) {
            if(r <= n && !vis[num[r]]) {
                vis[num[r]] = 1;
                r++;
            } else {
                rdif[l] = r - 1;
                vis[num[l]] = 0;
                l++;
            }
        }
        l = n, r = n;
        while(l >= 1) {
            if(r >= 1 && !vis[num[r]]) {
                vis[num[r]] = 1;
                r--;
            } else {
                ldif[l] = r + 1;
                vis[num[l]] = 0;
                l--;
            }
        }
        ans = 0;
        dfs(1, n);
        cout << ans << endl;
    }
}
posted @ 2020-06-14 00:28  limil  阅读(81)  评论(0编辑  收藏  举报