题目:
算法思路:
考虑每一个骨牌:倒下后产生区间
[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
为满足的最大值;
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
]
内有多少元素即可。
注:该代码未经验证正确性。如有疑问请发送邮件312715289@qq.com讨论。或有更简单方法欢迎致信。
#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;
}