题目链接:https://vjudge.net/problem/CodeChef-LAZER
题解:
可以用把询问离线,然后用扫描线的思想来解决这个问题。
把点和询问都丢到一个数组里面,按高度排序。
维护一个“当前高度”,遍历上述数组的过程就相当于让“当前高度”从低往高扫了一遍。
令树状数组中第i个点表示坐标为i的点和坐标为i+1的点间的线条是否包括了当前高度。
每当扫到线条的端点Pi,判断一下Pi左侧的端点Pi-1是否高度比Pi高,是则将Pi-1加到树状数组里,否则准备在下一次当前高度改变的时候把Pi-1从树状数组中删除。
当扫到一个询问[l, r]时,在树状数组中查询一下[l, r-1]的和作为答案就行了
代码:
#include <bits/stdc++.h>
#define maxn 100100
using namespace std;
int T, n, q, h[maxn], ans[maxn];
int tr[maxn+5];
queue<int> buf;
void add(int x, int v)
{
for (int i=x; i<maxn; i+=i&-i)
tr[i]+=v;
}
int query(int x)
{
int res=0;
for (int i=x; i; i-=i&-i)
res+=tr[i];
return res;
}
struct item
{
int f, qh, qx, qy, id;
};
bool cmp(item a, item b)
{
if (a.qh==b.qh)
return a.f<b.f;
return a.qh<b.qh;
}
int main()
{
ios::sync_with_stdio(0); cin.tie(0);
cin>>T;
while (T--)
{
cin>>n>>q;
vector<item> vec;
for (int i=1; i<=n; i++)
cin>>h[i];
for (int i=1; i<=n; i++)//表示点的f标为1
vec.push_back((item){1, h[i], i, 0, 0});
for (int i=1, qh, qx, qy; i<=q; i++)
{
cin>>qx>>qy>>qh;//表示询问的f标为2
vec.push_back((item){2, qh, qx, qy, i});
}
sort(vec.begin(), vec.end(), cmp);//按高度,种类排序
int cot=vec.size(), now=0;
for (int i=0; i<cot; i++)
{
if (vec[i].qh>now)
{//当前高度改变时要把buf中存的点从树状数组中删掉
now=vec[i].qh;
while (!buf.empty())
{
add(buf.front(), -1);
buf.pop();
}
}
if (vec[i].f==1)
{
//当前点左侧的点高度比它更高,则左侧的点加入树状数组
if (vec[i].qx>1 && h[vec[i].qx-1]>h[vec[i].qx])
add(vec[i].qx-1, 1);
//当前点左侧点高度比它低,则加入buf中(高度增高后两点间的线段就不再被经过了
else if (vec[i].qx>1) buf.push(vec[i].qx-1);
//考虑右侧,基本同上
if (vec[i].qx<n && h[vec[i].qx+1]>=h[vec[i].qx])
add(vec[i].qx, 1);
else if (vec[i].qx<n) buf.push(vec[i].qx);
}
//在树状数组中询问
if (vec[i].f==2)
ans[vec[i].id]=query(vec[i].qy-1)-query(vec[i].qx-1);
}
while (!buf.empty())
{//多组数据,所以最后把剩下的点从树状数组里删掉
add(buf.front(), -1);
buf.pop();
}
for (int i=1; i<=q; i++)
cout<<ans[i]<<"\n";
}
return 0;
}