(单点查询,单点更新)
题意:有一个h*w的广告牌,有很多1*w的小广告,贴广告的优先级是先上然后左,按顺序给一些广告,依次输出该广告被贴在第几行,如果不能贴的话,就输出-1
注意:因为题目中的h是10^8,这样的数组建树就非常大了,没有必要建到h,当h>n的时候,建到n就可以了。
分析,将a[i]表示第i行还剩下的位置,然后建树,那么线段树维护的是区间中的行剩的最多的位置。
using namespace std;
#define MAX 200005
struct node{
int l,r,maxn;
};
node d[MAX<<2];
int h,w,n;
void buildtree(int i,int l,int r)
{
d[i].l=l,d[i].r=r;
d[i].maxn=w;//初始化,最大值都等于广告牌的宽度
if(l==r) return ;
int mid=(l+r)/2;
buildtree(i<<1,l,mid);
buildtree(i<<1|1,mid+1,r);
}
int ans;
void Insert(int i,int wi)
{
if(wi>d[i].maxn)//如果比当前最大值还小的话,就说明插入不了了
{
ans=-1;
return ;
}
if(d[i].l==d[i].r)//对的,最后查询哪一行,也是点查询
{
d[i].maxn-=wi;
ans= d[i].l;//这里要注意,不是返回i
return ;
}
if (wi<=d[i<<1].maxn)
Insert(i<<1,wi);
else if(wi<=d[i<<1|1].maxn)
Insert(i<<1|1,wi);
d[i].maxn=max(d[i<<1].maxn,d[i<<1|1].maxn);//对值进行维护
}
int main()
{
while(scanf("%d %d %d",&h,&w,&n)!=EOF)
{
if(h>n)
h=n;
buildtree(1,1,h);
int wi;
for(int i=1;i<=n;i++)
{
ans=0;
scanf("%d",&wi);
Insert(1,wi);
printf("%d\n",ans);
}
}
return 0;
}
(单点更新,区间查询)
题意:
有两种操作, U A,B表示将A位置的值改成B,Q A B 表示查询区间A,B中最大连续递增序列
有1e5个数字,然后会操作1e5次。
线段树维护,最大连续递增长度,区间左端点的值,区间右端点的值;
using namespace std;
#define maxn 100005
int s[maxn];
int mlen[maxn<<2],lz[maxn<<2],rz[maxn<<2];
int dd(int i,int l,int r,int mid,int k)//k=1表示l,r是完整的,用于插入,//k=0,表示l,r不一定完整,用于查询;
{
int len1 = 0;
if(k&&mlen[i<<1]==(mid-l+1)) len1+=mid-l+1;
else {
for(int j=mid;j>=l;j--){//这里l不要...和1弄混...
if(s[j]1]) len1++;
else break;
}
}
if(k&&mlen[i<<1|1]==(r-mid)) len1+=r-mid;
else{
for(int j = mid+1;j<=r;j++){
if(s[j]>s[j-1]) len1++;
else break;
}
}
return len1;
}
void Insert(int i,int l,int r,int id,int x)
{
if(l==r){
lz[i]=rz[i]=x;mlen[i]=1;
s[l]=x;
return;
}
int mid = (l+r)>>1;
if(id<=mid) Insert(i<<1,l,mid,id,x);
else Insert(i<<1|1,mid+1,r,id,x);
if(rz[i<<1]1|1]){
int len1 = dd(i,l,r,mid,1);
mlen[i]=max(len1,max(mlen[i<<1],mlen[i<<1|1]));
}
else mlen[i]=max(mlen[i<<1],mlen[i<<1|1]);
lz[i]=lz[i<<1];rz[i]=rz[i<<1|1];
}
int Query(int i,int l,int r,int L,int R)
{
if(l==L&&r==R){
return mlen[i];
}
int mid = (l+r)>>1;
if(R<=mid) return Query(i<<1,l,mid,L,R);
else if(L>mid) return Query(i<<1|1,mid+1,r,L,R);
else{
int len1=Query(i<<1,l,mid,L,mid);
int len2=Query(i<<1|1,mid+1,r,mid+1,R);
int len3=dd(i,L,R,mid,0);
return max(len1,max(len2,len3));
}
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
int n,m,j,k;
char str[5];
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&s[i]);
Insert(1,1,n,i,s[i]);
}
while(m--)
{
scanf("%s %d %d",str,&j,&k);
if(str[0]=='U'){
Insert(1,1,n,j+1,k);
}
if(str[0]=='Q')
printf("%d\n",Query(1,1,n,j+1,k+1));
}
}
return 0;
}
(区间更新)参考博客
题意:n个数,m个操作,每次操作会有x,y,z三个数,表示区间x,y中的数都增加z,每次操作后输出整个区间中最长山形序列
山形序列:形似a[i] < a[i+1] < a[i+2] < … < a[k] > a[k+1]>a[k+2]>…
开始想复杂了…
这个题还是很巧妙的.
先对数组进行差分,可以将区间修改转化成点修改。因为差分之后,一个区间[l,r]被修改了,只会影响a[l]-a[l]和a[r+1]-a[r]
然后因为差分之后,线段树的维护也就方便了很多
线段树维护5个值,分别是
ans:最长的山形序列长度
lans:从左端点开始的山形序列的长度
rans:从右端点开始的山形序列的长度
ldown:从左端点开始的严格递减的长度
rup:从右端点开始的严格递减的长度
维护步骤:…看代码吧…画画图
还是简单讲一讲,
1.父节点的ldown=左孩子的ldown,如果左孩子的ldown=左孩子的区间长度,那么父节点的ldown = 左孩子的ldown+右孩子的ldown
2.父节点的rup = 右孩子的rup,如果右孩子的rup= 右孩子的区间长度,那么父节点的rup = 右孩子的rup+左孩子的rup
3.父节点的lans = 左孩子lans,如果左孩子的lans=左孩子的区间长度,则(如果左孩子的rup=左孩子的区间长度,那么父节点的lans=左孩子的lans+max(右孩子的lans,右孩子的ldown)),否则父节点的lans = 左孩子的lans+右孩子的ldown;)
4.父节点的rans = 右孩子的rans,如果右孩子的rans = 右孩子的区间长度,则(如果右孩子的ldwom = 右孩子的区间长度,那么父节点的rans = 右孩子的rans+max(左孩子的rans,左孩子的rup),否则父节点的lans = 右孩子的rans + 左孩子的rup);
5.父节点的ans = max(父节点的lans,父节点的rans,左孩子的ans,右孩子的ans,以及中间的山形长度);
using namespace std ;
#define ll long long
const int maxn=300000 + 10;
int ans[maxn<<2],lans[maxn<<2],rans[maxn<<2],ldown[maxn<<2],rup[maxn<<2];
ll a[maxn] , b[maxn] ;
int n , m ;
void update(int i,int l,int r,int mid)
{
ldown[i]=ldown[i<<1];
if(ldown[i<<1]==(mid-l+1))
ldown[i]+=ldown[i<<1|1];
rup[i] = rup[i<<1|1];
if(rup[i<<1|1]==(r-mid))
rup[i]+=rup[i<<1];
lans[i]=lans[i<<1];
if(lans[i<<1]==(mid-l+1)){
if(rup[i<<1]==(mid-l+1)) lans[i]+=max(ldown[i<<1|1],lans[i<<1|1]);
else lans[i]+=ldown[i<<1|1];
}
rans[i] = rans[i<<1|1];
if(rans[i<<1|1]==(r-mid)){
if(ldown[i<<1|1]==(r-mid)) rans[i]+=max(rans[i<<1],rup[i<<1]);
else rans[i]+=rup[i<<1];
}
ans[i]=max(lans[i],rans[i]);
ans[i]=max(ans[i],max(ans[i<<1],ans[i<<1|1]));
ans[i]=max(ans[i],rans[i<<1]+(rup[i<<1]? max(ldown[i<<1|1],lans[i<<1|1]):ldown[i<<1|1]));
ans[i]=max(ans[i],lans[i<<1|1]+((ldown[i<<1|1])?max(rup[i<<1],rans[i<<1]):rup[i<<1]));
}
void Build(int i,int l,int r)
{
if(l==r){
if(b[l]<0) ldown[i]=1;
else if(b[l]>0) rup[i]=1;
if(b[l]!=0) lans[i]=rans[i]=ans[i]=1;
return;
}
int mid = (l+r)>>1;
Build(i<<1,l,mid);
Build(i<<1|1,mid+1,r);
update(i,l,r,mid);
}
void Modify(int i,int l,int r,int id,int x){
if(l==r){
lans[i]=rans[i]=ans[i]=rup[i]=ldown[i]=0;
b[l]+=x;
if(b[l]<0) ldown[i]=1;
else if(b[l]>0) rup[i]=1;
if(b[l]!=0) lans[i]=rans[i]=ans[i]=1;
return;
}
int mid = (l+r)>>1;
if(id<=mid) Modify(i<<1,l,mid,id,x);
else Modify(i<<1|1,mid+1,r,id,x);
update(i,l,r,mid);
}
int main() {
scanf( "%d" , &n ) ;
for (int i = 1 ; i <= n ; i ++ ) cin >> a[i] ;
a[0] = a[1] ;
for (int i = 1 ; i <= n ; i ++ ) b[i] = a[i] - a[i-1] ;
Build( 1 , 1 , n ) ;
scanf( "%d" , &m ) ;
for (int i = 1 ; i <= m ; i ++ ) {
int l , r , del ;
scanf( "%d%d%d" , &l , &r , &del ) ;
if ( l > 1 ) Modify( 1 , 1 , n , l , del ) ;
if ( r < n ) Modify( 1 , 1 , n , r + 1 , -del ) ;
printf( "%d\n" , ans[1]+ 1 ) ;
}
return 0 ;
}
题意:
有一个队伍,每个人有独一的高度,打散之后,她们只记得前面或者后面比自己高的人数k,给每个人的高度和k,问是否存在符合所有人条件的序列,并输出字典序最小的高度序列,不能的话输出impossible
分析:因为每个人只记得自己前面或者后面比自己高的人数,那么比自己矮的就没什么影响;需要来确定每个人的位置,那么需要知道一个区间中位置占用情况,那么就需要用到线段树了。
线段树维护的是区间剩余的位置
因为需要字典序最小,那么先按照身高从小到大排序,按顺序放入,对于当前身高a[i].h来说,还有c个空位,那么肯定是尽可能放在前面,因为后面的身高都会大于他,就不是最小字典序了。所以考虑一下是前面高人是a[i].k,还是后面高人是a[i].k
using namespace std;
#define ll long long
#define mem(a,b) memset(a,b,sizeof(a))
const int maxn = 1e5+10;
struct node{
int h,k;
}a[maxn];
int v[maxn<<2];//应该维护剩余的
bool cmp(node a1,node a2){
return a1.hint ans[maxn];
void sert(int i,int l,int r,int x,int id){
if(l==r){
ans[l]=a[id].h;
v[i]=0;
return;
}
int mid = (l+r)>>1;
if(v[i<<1]>x) sert(i<<1,l,mid,x,id);
else sert(i<<1|1,mid+1,r,x-v[i<<1],id);
v[i]=v[i<<1]+v[i<<1|1];
}
void build(int i,int l,int r){
if(l==r){
v[i]=1;return;
}
int mid = (l+r)>>1;
build(i<<1,l,mid);
build(i<<1|1,mid+1,r);
v[i]=v[i<<1]+v[i<<1|1];
}
int main()
{
int T,k=0;
scanf("%d",&T);
while(T--){
mem(v,0);mem(ans,0);mem(a,0);
int n,flag=0;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d %d",&a[i].h,&a[i].k);
}
sort(a+1,a+n+1,cmp);
build(1,1,n);
for(int i=1;i<=n;i++){
if(a[i].k>(n-i)){flag=1;break;}
int p = min(a[i].k,n-i-a[i].k);
sert(1,1,n,p,i);
}
printf("Case #%d:",++k);
if(flag)printf(" impossible\n");
else {
for(int i=1;i<=n;i++)
printf(" %d",ans[i]);
printf("\n");
}
}
return 0;
}