1. 程式人生 > >2018年9月3日 貝殼筆試題(線段樹+二分查詢)

2018年9月3日 貝殼筆試題(線段樹+二分查詢)

題目:
這裡寫圖片描述

演算法思路:
考慮每一個骨牌:倒下後產生區間[xi,xi+hi1]。 區間內會連帶後面未知長度區間的骨牌倒下。假設最後倒下的區間為[xi,farthesti]
首先很容易寫出時間複雜度為O(n2)(肯定超時)的動規方程:dpi=maxj=i+1kdpj其中k為滿足xk<=xi+hi1的最大值;dpi表示第i個骨牌倒下後,後面所有連帶倒下骨牌的最右端的x座標。優化該方程求最大值部分需要引入線段樹優化時間複雜度。線段樹節點儲存的值為區間

[a,b]內所有骨牌倒下後,之後接連倒下骨牌的最遠距離(即最大x下標值)。需要說明一點,線段樹初始化流程:葉子節點初始化root[i]=xi+hi1。之後單點更新葉子值為root[i]=max(root[i],query(i,k))(這裡的時間複雜度為O(nlog2n),時間妥妥夠)。這裡查詢葉子k使用二分查詢,搜尋xk為不大於xi+hi1的最小值;query為區間內連帶倒下骨牌的最遠距離。注意這裡i中的i應從大到小更新。
整棵線段樹更新完畢後即可查詢區間最遠連帶倒下骨牌的下標
farthesti
。然後有了farthesti 即可用二分查詢,搜尋區間[xi,farthesti]內有多少元素即可。

注:該程式碼未經驗證正確性。如有疑問請傳送郵件[email protected]討論。或有更簡單方法歡迎致信。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

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;i<n;i++){ printf("%d ", nodes[i].ans); } printf("%d\n", nodes[n].ans); } return 0; }