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

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6701

解题心得:

  • 这个题首先要明白是用分治去解决,也就是说如果区间 [ l , r ] [l,r] [l,r]之间最大值在位置 p o s pos pos,那么可以枚举当左端点为 i ( l &lt; = i &lt; = p o s ) i(l&lt;=i&lt;=pos) i(l<=i<=pos)得到包含 p o s pos pos的右端点(固定左端点算贡献),按照题目中所给的关系式和不重复的规定得到符合条件右端点为一段区间 [ L , R ] [L,R] [L,R],如果 L &lt; = R L&lt;=R L<=R,那么说明这个 i i i点对于最后的答案有贡献,那么最终答案加上( R − L + 1 R-L+1 RL+1)。算出当前 [ l , r ] [l,r] [l,r]之后递归下去计算 [ l , m i d − 1 ] [l,mid-1] [l,mid1] [ m i d + 1 , r ] [mid+1,r] [mid+1,r]
  • 其次有个问题就是怎么可以直接找到区间 [ l , r ] [l,r] [l,r]的最大值和最大值的位置,这个可以先处理出一个ST表,这样就可以直接得到了。并且关于一点 i i i的不重复左右区间也可以先预处理出来。
  • 最后分治的时候有个问题就是当所给出的 n n n个数为不重复升序排列的时候,那么分治仅仅是每次将右端点向左移动一位并且每次枚举固定左端点复杂度就成了 O ( l o g n 2 ) O(logn^{2}) O(logn2)。这个时候就需要启发式优化,也就是说我们找到区间 [ l , r ] [l,r] [l,r]之间最大值在位置 p o s pos pos,那么算贡献的时候可以固定左端点计算右端点的个数,也可以固定右端点计算左端点的个数,固定左/右端点需要枚举的区间分别可以是 [ l , m i d ] [l, mid] [l,mid] [ m i d , r ] [mid,r] [mid,r],那么假设枚举规则是哪个区间小枚举哪个区间,这时递归下去的时候复杂度越坏计枚举算次数越少,这就将最坏的情况进行了限制,真的是非常精妙的优化啊。


#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 3e5+100;

int num[maxn], maxSt[maxn][30], posSt[maxn][30], dR[maxn], dL[maxn], n, k;
ll ans = 0;

void init() {
    ans = 0;
    scanf("%d%d", &n, &k);
    for(int i=1;i<=n;i++) scanf("%d", &num[i]);
}

void getStTable() {
    for(int i=1;i<=n;i++) {
        maxSt[i][0] = num[i];
        posSt[i][0] = i;
    }

    for(int j=1;j<=30;j++) {
        for(int i=1;i+(1<<j)-1<=n;i++) {
            int Max1 = maxSt[i][j - 1];
            int Max2 = maxSt[i + (1 << (j - 1))][j - 1];
            if (Max1 >= Max2) {
                maxSt[i][j] = Max1;
                posSt[i][j] = posSt[i][j - 1];
            } else {
                maxSt[i][j] = Max2;
                posSt[i][j] = posSt[i+(1<<(j-1))][j-1];
            }
        }
    }
}

bool vis[maxn];
void getLRPos() {
    int pos = 2;
    vis[num[1]] = true;
    for(int i=1;i<=n;i++) {
        while(pos <= n && !vis[num[pos]]) {
            vis[num[pos]] = true;
            pos++;
        }
        dR[i] = pos-1;
        vis[num[i]] = false;
    }
    memset(vis, 0, sizeof vis);
    pos = n-1;
    vis[num[n]] = true;
    for(int i=n;i>=1;i--) {
        while(pos >= 1 && !vis[num[pos]]) {
            vis[num[pos]] = true;
            pos--;
        }
        dL[i] = pos+1;
        vis[num[i]] = false;
    }
}

int getPos(int l, int r) {
    int Log = 0, Pow = 1;
    while(Pow <= (r-l+1)) {
        Pow <<= 1;
        Log++;
    }
    Log--;

    int Max1 = maxSt[l][Log];
    int Max2 = maxSt[r-(1<<Log)+1][Log];
    if(Max1 >= Max2) return posSt[l][Log];
    else return posSt[r-(1<<Log)+1][Log];
}

void getCnt(int l, int r) {
    if(l > r) return ;
    int mid = getPos(l, r);
    if(r - mid > mid - l) {
        for (int i = l; i <= mid; i++) {
            int L = max(mid, num[mid] - k + i - 1);
            int R = min(dR[i], r);

            if (L <= R) ans += R - L + 1;
        }
    } else {
        for(int i=r;i>=mid;i--) {
            int R = min(mid, k+1+i-num[mid]);
            int L = max(dL[i], l);

            if(L <= R) ans += R-L+1;
        }
    }
    getCnt(l, mid-1);
    getCnt(mid+1, r);
}

int main() {
//    freopen("1.in.txt", "r", stdin);
    int t; scanf("%d", &t);
    while(t--) {
        init();
        getStTable();
        getLRPos();
        getCnt(1, n);
        printf("%lld\n", ans);
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值