题目链接:
http://acm.hdu.edu.cn/showproblem.php?pid=4417
解题思路:
题目大意:
一个长为n的路,每个整点处有高度为h的墙,现在超级玛丽要跳过这些墙去救公主。给m个询问,每个询问包含询问区间a-b以及玛丽的弹跳高度c,求对于每个询问,玛丽能跳过这个区间多少墙。
算法思想:
线段树+二分查找可以做,也可以套POJ2104(区间第k大数的模板),然后再加二分查找也可以做出来。。。
AC代码(线段树+二分查找):
#include
#include
#include
#include
using namespace std;
const int maxn = 100005;
int n,m;
int a[maxn];
vector v[maxn<<2];
vector::iterator it;
void build(int id,int l,int r){
v[id].clear();
for(int i = l; i <= r; i++)//把 l 到 r 的元素都存在vector中
v[id].push_back(a[i]);
sort(v[id].begin(),v[id].end());
if(l == r)
return;
int mid = (l+r)>>1;
build(id<<1,l,mid);
build(id<<1|1,mid+1,r);
}
int query(int id,int L,int R,int l,int r,int h){
if(l <= L && R <= r){
//当查待询区间[L, R] 完全包含 [l, r] 则返回该区间的结果
it = upper_bound(v[id].begin(),v[id].end(),h);
//找到第一个比val大的位置
return it - v[id].begin();
//相减得到小于等于val的个数
}
int mid = (L+R)>>1;
int ans = 0;
if(l <= mid)
ans += query(id<<1,L,mid,l,r,h);
// 有一部分区间在 [L,mid] 之间
if(mid < r)
ans += query(id<<1|1,mid+1,R,l,r,h);
//有一部分在 [mid+1, R] 之间
return ans;
}
int main(){
int T,t = 1;
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&m);
for(int i = 1; i <= n; i++)
scanf("%d",&a[i]);
build(1,1,n);
printf("Case %d:\n",t++);
int l,r,h;
while(m--){
scanf("%d%d%d",&l,&r,&h);
printf("%d\n",query(1,1,n,l+1,r+1,h));//题目的输入是从[0,n-1]开始的,我们需要将数据转化到[1,n]
}
}
return 0;
}
AC代码(划分树+二分查找):
#include
#include
#include
using namespace std;
const int N = 100005;
struct node{
int l,r,mid;
}tree[N<<2];
int sa[N],num[20][N],cnt[20][N];//sa中是排序后的,num记录每一层的排序结果,cnt[deep][i]表示第deep层,前i个数中有多少个进入左子树
/*
void debug(int d,int n){
for(int i = 1; i <= n; i++)
printf("%d ",num[d][i]);
printf("\n");
}
*/
void build(int m,int l,int r,int deep){
tree[m].l = l;
tree[m].r = r;
if(l == r)
return ;
int mid = (l+r)>>1;
int mid_val = sa[mid],lsum = mid-l+1;
for(int i = l; i <= r; i++)
if(num[deep][i] < mid_val)
lsum--;//lsum表示左子树中还需要多少个中值
int L = l,R = mid+1;
for(int i = l; i <= r; i++){
if(i == l)
cnt[deep][i] = 0;
else
cnt[deep][i] = cnt[deep][i-1];
if(num[deep][i] < mid_val || (num[deep][i] == mid_val && lsum > 0)){
//左子树
num[deep+1][L++] = num[deep][i];
cnt[deep][i]++;
if(num[deep][i] == mid_val)
lsum--;
}
else
num[deep+1][R++] = num[deep][i];
}
//debug(deep);
build(m<<1,l,mid,deep+1);
build(m<<1|1,mid+1,r,deep+1);
}
int query(int m,int l,int r,int deep,int k){
if(l == r)
return num[deep][l];
int s1,s2;//s1为[tree[step].left,l-1]中分到左子树的个数
if(tree[m].l == l)
s1 = 0;
else
s1 = cnt[deep][l-1];
s2 = cnt[deep][r]-s1;//s2为[l,r]中分到左子树的个数
if(k <= s2)//左子树的数量大于k,递归左子树
return query(m<<1,tree[m].l+s1,tree[m].l+s1+s2-1,deep+1,k);
int b1 = l-1-tree[m].l+1-s1;//b1为[tree[m].l,l-1]中分到右子树的个数
int b2 = r-l+1-s2; //b2为[l,r]中分到右子树的个数
int mid = (tree[m].l+tree[m].r)>>1;
return query(m<<1|1,mid+1+b1,mid+1+b1+b2-1,deep+1,k-s2);
}
int solve(int L,int R,int h){
int l = 1,r = R-L+1;
int ans = 0;
while(l <= r){
int mid = (l+r)>>1;
/*
//如果问在查找区间[s,t]大于h的个数有多少
// 方法一:
if(query(1,L,R,1,mid) >= h){ //if第mid小的数都比h来得大那至少有(t-s+1)-mid+1个
ans=(t-s+1)-mid+1;//自己画一下就能够理解了
r=mid-1;
}
else
l=mid+1;//if第mid小的数不大于h 往后面找
//方法2:
* query方法改成得到区间[s,t]的第mid*****大*****的数(上面有讲只需要改个符号即可)
if(query(1,L,R,1,mid) <= h){ //if第mid大的数h大;那至少有mid个数比h大
ans=mid//
l=mid+1;//往后找
}
else
r=mid-1;//否则无法知道至少几个,往前面找更大的数与之比较
*/
if(query(1,L,R,1,mid) <= h){
l = mid+1;
ans = mid;
}
else
r = mid-1;
}
return ans;
}
int main(){
int T,t = 1;
scanf("%d",&T);
while(T--){
int n,q;
scanf("%d%d",&n,&q);
for(int i = 1; i <= n; i++){
scanf("%d",&num[1][i]);
sa[i] = num[1][i];
}
sort(sa+1,sa+n+1);
build(1,1,n,1);
printf("Case %d:\n",t++);
while(q--){
int l,r,h;
scanf("%d%d%d",&l,&r,&h);
l++;r++;//题目的输入是从[0,n-1]开始的,我们需要将数据转化到[1,n]
printf("%d\n",solve(l,r,h));
}
}
return 0;
}