2018年9月3日 贝壳笔试题(线段树+二分查找)

题目:
2018年9月3日 贝壳笔试题(线段树+二分查找)_第1张图片

算法思路:
考虑每一个骨牌:倒下后产生区间 [xi,xi+hi1] [ x i , x i + h i − 1 ] 。 区间内会连带后面未知长度区间的骨牌倒下。假设最后倒下的区间为 [xi,farthesti] [ x i , f a r t h e s t i ]
首先很容易写出时间复杂度为 O(n2) O ( n 2 ) (肯定超时)的动规方程: dpi=maxkj=i+1dpj d p i = m a x j = i + 1 k d p j 其中 k k 为满足 xk<=xi+hi1 x k <= x i + h i − 1 的最大值; dpi d p i 表示第i个骨牌倒下后,后面所有连带倒下骨牌的最右端的x坐标。优化该方程求最大值部分需要引入线段树优化时间复杂度。线段树节点保存的值为区间 [a,b] [ a , b ] 内所有骨牌倒下后,之后接连倒下骨牌的最远距离(即最大x下标值)。需要说明一点,线段树初始化流程:叶子节点初始化 root[i]=xi+hi1 r o o t [ 叶 子 i ] = x i + h i − 1 。之后单点更新叶子值为 root[i]=max(root[i],query(i,k)) r o o t [ 叶 子 i ] = m a x ( r o o t [ 叶 子 i ] , q u e r y ( 叶 子 i , 叶 子 k ) ) (这里的时间复杂度为 O(nlogn2) O ( n l o g 2 n ) ,时间妥妥够)。这里查找叶子k使用二分查找,搜索 xk x k 为不大于 xi+hi1 x i + h i − 1 的最小值;query为区间内连带倒下骨牌的最远距离。注意这里 i 叶 子 i 中的i应从大到小更新。
整棵线段树更新完毕后即可查询区间最远连带倒下骨牌的下标 farthesti f a r t h e s t i 。然后有了 farthesti f a r t h e s t i 即可用二分查找,搜索区间 [xi,farthesti] [ x i , f a r t h e s t i ] 内有多少元素即可。

注:该代码未经验证正确性。如有疑问请发送邮件[email protected]讨论。或有更简单方法欢迎致信。

#include 
#include 
#include 
#include 

using namespace std;
#define INF 1e7
#define N 100010
#define M 100010

struct Node {
    int x, h;
    int id, ans;
}nodes[N];

bool comp1(Node a, Node b) {
    return a.x < b.x;
    //<升序,>降序,a.x以x排序
}

bool comp2(Node a, Node b) {
    return a.id < b.id;
    //<升序,>降序,a.id以a排序
}

int max(int a, int b){
    return a>b?a:b;
}

int binary_search(int l, int r, int value){
    //printf("%d,%d\n",l,r);
    if(l==r) return l;
    int mid = ( l + r ) / 2;
    if( value <= nodes[mid].x )
        return binary_search(l, mid, value);
    return binary_search(mid+1, r, value);
}

int tree[4 * N];

void update(int n) {
    tree[n] = max( tree[n << 1], tree[n << 1 | 1] );
}

void build_tree(int left, int right, int n) {
    if (left == right) {
        //cin >> tree[left];
        tree[n] = nodes[left].x + nodes[left].h;
        return;
    }
    int mid = (left + right) >> 1;
    build_tree(left, mid, n << 1);
    build_tree(mid + 1, right, n << 1 | 1);
    update(n);
}

// 单点更新操作; left一般恒等于1,right一般恒等于n,n一般恒等于1
void add(int a, int b, int left, int right, int n) {
    if (left == right) {
        tree[n] = max(tree[n], b);
        return;
    }
    int mid = (left + right) >> 1;
    if (a <= mid) {
        add(a, b, left, mid, n << 1);
    } else {
        add(a, b, mid + 1, right, n << 1 | 1);
    }
    update(n);
}

// l为要查找的区间左边界; left一般恒等于1,right一般恒等于n,n一般恒等于1
int query(int l, int r, int left, int right, int n) {
    if (l <= left && r >= right) return tree[n];
    int mid = (left + right) >> 1;
    int max_ = max(tree[left],tree[right]);
    if (l <= mid) {
        max_ = max(max_, query(l, r, left, mid, n << 1));
    }
    if (r > mid) {
        max_ = max(max_, query(l, r, mid + 1, right, n << 1 | 1));
    }
    return max_;
}

void work(int n){
    build_tree(1,n,1);
    for(int i = n;i>=1;i--){ //从右向左更新单点
        int right_id = binary_search(1,n,nodes[i].x+nodes[i].h-1);
        int max_ = query(i,right_id,1,n,1);
        add(i,max_,1,n,1);
    }
    for(int i =1;i<=n;i++){
        // 查找x[k] <= x[i]+h[i]-1的最大k值
        int right_id = binary_search(1,n,nodes[i].x+nodes[i].h-1);
        // 查找i到i倒下区间内连带倒下的骨牌最远距离
        int max_ = query(i,right_id,1,n,1);
        // 二分查找x[i]到该最远距离之间有多少个元素
        int max_right_id = binary_search(1,n,max_);
        nodes[i].ans = max_right_id - i + 1;
    }
}

int main() {
    int n, m;
    freopen("../test.txt", "r", stdin);
    while( ~scanf("%d", &n) ){
        for(int i = 1;i<=n;i++){
            scanf("%d%d", &nodes[i].x,&nodes[i].h);
            nodes[i].id = i;
        }
        sort(nodes+1, nodes+n+1, comp1); // 按照x值排序
        work(n);
        sort(nodes+1, nodes+n+1, comp2); // 按照输入id值排序
        for(int i =1;iprintf("%d ", nodes[i].ans);
        }
        printf("%d\n", nodes[n].ans);
    }
    return 0;
}

你可能感兴趣的:(2018年9月3日 贝壳笔试题(线段树+二分查找))