题解 P4392 【[BOI2007]Sound 静音问题】
超级树状数组
对于大部分oier来说,码线段树是一件十分困难的事情,对于这一个OB的算法又不得不去学习
而对于另一种友好的树形结构——树状数组就特别好码,可惜的是只能去求区间的和,以及单点修改
真的是这样吗?树状数组不是什么鸡肋,其实可以和线段树有一样的操作,对于区间的最大最小,是可以手动维护的
void add(int p,int x)//添加一个数(不能修改)
{
for(;p<=n;p+=p&(-p))
{
c[p].mx=max(c[p].mx,x);//维护区间的最大以及最小
c[p].mn=min(c[p].mn,x);
}
}
之后求和就好了
bool sum(int l,int r)
{
int mx=-0x7f7f7f7f,mn=0x7f7f7f7f;
while(l<=r)
{
for(;r-(r&(-r))>=l;r-=r&(-r))//标配
{
mx=max(c[r].mx,mx);
mn=min(c[r].mn,mn);
}
mx=max(mx,a[r]);
mn=min(mn,a[r]);
r--;//不是所有区间都已处理,所以会多次维护
}
if(abs(mx-mn)<=ci)
return true;
return false;
}
a下这到题目只需要上述两个操作
下面ac代码
#include<bits/stdc++.h>
using namespace std;
struct tree{
int mx=-0x7f7f7f7f,mn=0x7f7f7f7f,a;
}c[1100101];
int n,m,ci,ans[1000001],a[1000001],cnt;
void add(int p,int x)
{
for(;p<=n;p+=p&(-p))
{
c[p].mx=max(c[p].mx,x);
c[p].mn=min(c[p].mn,x);
}
}
bool sum(int l,int r)
{
int mx=-0x7f7f7f7f,mn=0x7f7f7f7f;
while(l<=r)
{
for(;r-(r&(-r))>=l;r-=r&(-r))
{
mx=max(c[r].mx,mx);
mn=min(c[r].mn,mn);
}
mx=max(mx,a[r]);
mn=min(mn,a[r]);
r--;
}
if(abs(mx-mn)<=ci)
return true;
return false;
}
int main()
{
//freopen("in.txt","r",stdin);
scanf("%d%d%d",&n,&m,&ci);
for(int i=1;i<=n;i++)
{
int x;
scanf("%d",&a[i]);
add(i,a[i]);
}
for(int i=1;i<=n-m+1;i++)
{
if(sum(i,i+m-1))
ans[++cnt]=i;
}
for(int i=1;i<=cnt;i++)
{
printf("%d\n",ans[i]);
}
if(cnt==0)
cout<<"NONE";
}
其实怎么做的原因,手动模拟一下树状数组的维护操作就可以知道
其实我们还可以进行区间修改以及查询
void add(int c[],int p,int x)
{
while(p<=n)
{
c[p]+=x;
p+=p&(-p);
}
}
void modify(int l,int r,int x)//区间修改(l——r的每一个数加上x)
{
add(d1,l,x);
add(d1,r+1,-x);
add(d2,l,x*(l-1));
add(d2,r+1,-x*r);
}
int sum_(int c[],int p)
{
int s=0;
while(p)
{
s+=c[p];
p-=p&(-p);
}
return s;
}
int query(int l,int r)//区间求和(l——r)
{
return r*sum_(d1,r)-sum_(d2,r)-((l-1)*sum_(d1,l-1)-sum_(d2,l-1));//想一想,为什么(huaji)
}
那么我们就用简单易懂的树状数组代替了线段树,由于代码量少,认真思考一两分钟就可以得出原因的代码,就不多加阐述
在考场上,面对功能极度全面的线段树O(Ologn),以及代码量少的树状数组O(nlognlogn),加以权衡
其实想要详细解释,可以去看洛谷日报22期,有说明和图的