算法思路:
考虑每一个骨牌:倒下后产生区间 [xi,xi+hi−1] [ 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+hi−1 x k <= x i + h i − 1 的最大值; dpi d p i 表示第i个骨牌倒下后,后面所有连带倒下骨牌的最右端的x坐标。优化该方程求最大值部分需要引入线段树优化时间复杂度。线段树节点保存的值为区间 [a,b] [ a , b ] 内所有骨牌倒下后,之后接连倒下骨牌的最远距离(即最大x下标值)。需要说明一点,线段树初始化流程:叶子节点初始化 root[叶子i]=xi+hi−1 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+hi−1 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;
}