Removing Stones (牛客多校) (启发式分治 .二分)

题目大意:

  • 给出n堆石头, 给出每堆石头的数量, 问有多少对合法的 l,r 
  • 使得L,R 区间内的最大值的*2 < =区间总和

 

 

 思路:

  • 从最大值入手, ->为了方便处理 就利用分治 去 处理出当前的最大值要考虑区间范围是那些
  • 首先通过st 表来预处理 出 l,r 的最大值的位置
  • 分治 dfs(l,r) 判断当前有多少个符合的区间时, 可以去枚举 左端点或者右端点的 边界值 (谁范围小选谁),然后再去二分另外一个值, ()
  • 然后在把区间分为 左右2个部分即可
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn = 3e5+5;
int st[maxn][25];//st表
int n, cas;//题目所给变量
ll ans = 0;//答案
ll a[maxn], sum[maxn];//a为原序列,sum为前缀和序列
int lg[300005] = {-1};//st表需要的lg数组
void init(){//初始化st表
    for(int i = 1; i <= n; i++){
        st[i][0] = i;
    } 
    for(int j = 1; (1 << j) <= n; j++){
        for(int i = 1; i + (1 << (j-1)) <= n; i++){
            st[i][j] = (a[st[i][j-1]] > a[st[i+(1<<(j-1))][j-1]])? st[i][j-1]: st[i+(1<<(j-1))][j-1];
        }
    }
}
int query_max_place(int l, int r){//st表求最大值位置
    int k = lg[r-l+1];
    return (a[st[l][k]] > a[st[r-(1<<k)+1][k]])? st[l][k]: st[r-(1<<k)+1][k];
}
void dfs(int L, int R){//求解l~r范围内满足条件的子串的数量
    if(L >= R)   return;//左边界要在右边界的左边
    int k = query_max_place(L, R);//ST表求最大值位置
    if(R + L > 2 * k){//判断最大值位置靠近左边还是右边,下面是靠近左边的处理方式
        for(int i = L; i <= k; i++){//枚举左边界
            if(a[k] > ((sum[R] - sum[i-1]) >> 1))  break;//判断是否存在合法右边界
            int l = k, r = R;//二分区域
            while(l < r){//二分
                int mid = (l + r) >> 1;
                if(a[k] > ((sum[mid] - sum[i-1]) >> 1)){//判断是否满足最大值不超过当前区域总和的一半
                    l = mid + 1;
                }
                else    r = mid;
            }
            ans += 1ll * (R - l + 1);//子串数量加到ans中
        }
    }
    else{ //下面是最大值靠近右边界的处理,与靠近左边界类似
        for(int i = R; i >= k; i--){
            if(a[k] > ((sum[i] - sum[L-1]) >> 1))  break;
            int l = L, r = k;
            while(l < r){
                int mid = (l + r + 1) >> 1;
                if(a[k] > ((sum[i] - sum[mid-1]) >> 1)){
                    r = mid - 1;
                }
                else    l = mid;
            }
            ans += 1ll * (l - L + 1);
        }
    }
      
    dfs(L, k-1);//答案加上子串2的数量
    dfs(k+1, R);//答案加上子串3的数量
    return;
} 
int main(){
    for(int i = 1; i <= 300005; i++) lg[i] = lg[i/2] + 1;//获得lg数组
    cin >> cas;
    while(cas--){
        ans = 0;
        scanf("%d", &n);
        for(int i = 1; i <= n; i++){//获得前缀和数组
            scanf("%d", &a[i]);
            sum[i] = sum[i-1] + a[i];
        } 
        init();//初始化st表
        dfs(1, n);//求1~n范围内合法的子串数量
        cout << ans << endl;
    }
    return 0;
}
别人的代码
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn = 3e5+5;
int st[maxn][25];//st表
int n, cas;//题目所给变量
ll ans = 0;//答案
ll a[maxn], sum[maxn];//a为原序列,sum为前缀和序列
int lg[300005] = {-1};//st表需要的lg数组
void init(){//初始化st表
    for(int i = 1; i <= n; i++){
        st[i][0] = i;
    } 
    for(int j = 1; (1 << j) <= n; j++){
        for(int i = 1; i + (1 << (j-1)) <= n; i++){
            st[i][j] = (a[st[i][j-1]] > a[st[i+(1<<(j-1))][j-1]])? st[i][j-1]: st[i+(1<<(j-1))][j-1];
        }
    }
}
int query_max_place(int l, int r){//st表求最大值位置
    int k = lg[r-l+1];
    return (a[st[l][k]] > a[st[r-(1<<k)+1][k]])? st[l][k]: st[r-(1<<k)+1][k];
}
void dfs(int L, int R){//求解l~r范围内满足条件的子串的数量
    if(L >= R)   return;//左边界要在右边界的左边
    int k = query_max_place(L, R);//ST表求最大值位置
    if(R + L > 2 * k){//判断最大值位置靠近左边还是右边,下面是靠近左边的处理方式
        for(int i = L; i <= k; i++){//枚举左边界
            if(a[k] > ((sum[R] - sum[i-1]) >> 1))  break;//判断是否存在合法右边界
            int l = k, r = R;//二分区域
            while(l < r){//二分
                int mid = (l + r) >> 1;
                if(a[k] > ((sum[mid] - sum[i-1]) >> 1)){//判断是否满足最大值不超过当前区域总和的一半
                    l = mid + 1;
                }
                else    r = mid;
            }
            ans += 1ll * (R - l + 1);//子串数量加到ans中
        }
    }
    else{ //下面是最大值靠近右边界的处理,与靠近左边界类似
        for(int i = R; i >= k; i--){
            if(a[k] > ((sum[i] - sum[L-1]) >> 1))  break;
            int l = L, r = k;
            while(l < r){
                int mid = (l + r + 1) >> 1;
                if(a[k] > ((sum[i] - sum[mid-1]) >> 1)){
                    r = mid - 1;
                }
                else    l = mid;
            }
            ans += 1ll * (l - L + 1);
        }
    }
      
    dfs(L, k-1);//答案加上子串2的数量
    dfs(k+1, R);//答案加上子串3的数量
    return;
} 
int main(){
    for(int i = 1; i <= 300005; i++) lg[i] = lg[i/2] + 1;//获得lg数组
    cin >> cas;
    while(cas--){
        ans = 0;
        scanf("%d", &n);
        for(int i = 1; i <= n; i++){//获得前缀和数组
            scanf("%d", &a[i]);
            sum[i] = sum[i-1] + a[i];
        } 
        init();//初始化st表
        dfs(1, n);//求1~n范围内合法的子串数量
        cout << ans << endl;
    }
    return 0;
}
别人的代码

 启发式关键:

  • 处理 极值(最大,最小,1 ....) 关于区间点对问题, 
  • 通过极值来吧区间分成2部分(递归处理)来做, 关键在于思考如何利用 当前区间的一半时间复杂度,或者尽可能小的时间复杂度去处理问题, 目前可以接受的时候 一半*ln(一半)
posted @ 2023-04-13 16:14  VxiaohuanV  阅读(12)  评论(0编辑  收藏  举报