线段树的进阶:多种信息的维护与传递

 山海经

时间限制:1 s   内存限制:128 MB

【问题描述】

“南山之首曰鹊山。其首曰招摇之山,临于西海之上,多桂,多金玉。有草焉,其状如韭而青华,其名曰祝余,食之不饥……又东三百里,曰堂庭之山,多棪木,多白猿,多水玉,多黄金。

又东三百八十里,曰猨翼之山,其中多怪兽,水多怪鱼,多白玉,多蝮虫,多怪蛇,名怪木,不可以上。……”

《山海经》是以山为纲,以海为线记载古代的河流、植物、动物及矿产等情况,而且每一条记录路线都不会有重复的山出现。某天,你的地理老师想重游《山海经》中的路线,为了简化问题,老师已经把每座山用一个整数表示他对该山的喜恶程度,他想知道第a座山到第b座山的中间某段路(i,j)。能使他感到最满意,即(i,j)这条路上所有山的喜恶度之和是(c,d)(a≤c≤d≤b)最大值。于是老师便向你请教,你能帮助他吗?值得注意的是,在《山海经》中,第i座山只能到达第i+1座山。

【输入】

输入第1行是两个数,n,m,2≤n≤100000,1≤m≤100000,n表示一共有n座山,m表示老师想查询的数目。

第2行是n个整数,代表n座山的喜恶度,绝对值均小于10000。

以下m行每行有a,b两个数,1≤a≤j≤b≤m,表示第a座山到第b座山。

【输出】

一共有m行,每行有3个数i,j,s,表示从第i座山到第j座山总的喜恶度为s。显然,对于每个查询,有a≤i≤j≤b,如果有多组解,则输出i最小的,如果i也相等,则输出j最小的解。

【输入样例】

5 3 

5 -6 3 -1 4

1 3

1 5

5 5

【输出样例】

1 1 5

3 5 6

5 5 4

 

【题解】

思路

z:左连续最大                      y:右连续最大
v:权值和                             a:中间最大
al:中间最大左边界             ar:中间最大右边界
zj:左连续最大右边界          yj:右连续最大左边界
ls:左儿子                           rs:右儿子

建树部分
1.各叶子结点z、y、a、v均为它本身权值;
zj、yj、al、ar均为它本身下标。
2.非叶子节点
v=ls->v+rs->v
a=max(ls->a、rs->a、ls->y+rs->z)
依据选了什么来更新ar、al
z=max(ls->z,rs->z+ls->v)'
y同理,并根据这个来更新zj、yj

zb:答案左边界                 yb:答案右边界

询问部分
1.确切区间:更新全局变量z、y、zj、yj、zb、yb,以备大区间合并使用
返回a
2.合并区间:分左右询问子区间
先询问左区间,用局部变量记录各全局变量此时的值
再询问右区间
依然向建树时合并区间一样处理

经历

1WA:思路根本就很乱
2WA:没有按i的字典序
3WA:没有初始化负无穷
4WA:没有在i相同的时候按j的字典序

细节

          1.处理i的字典序:当有值相同时总是按照先左后中最后右的原则进行(包括建树时的a、z都尽量靠左,询问时优先考虑ls的答案,具体方法是先入为主后来的只有比它大才能更新)
          2.处理j的字典序:有它没它都一样肯定说明它是0了,特判一下答案右边界对应的喜爱值是否为0,是0则把右边界--即可
          3.初始化-0x7f,防止建树时一段都是负数无法记录azy等变量

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int sj=100010;
int n,m,li[sj],ans,zb,yb,zi,yi,zj,yj,vi;
struct node
{
     int l,r,v;
     int al,ar,a;
     int zj,yj,zm,ym;
}t[sj*4];
void build(int x,int l,int r)
{
     t[x].l=l,t[x].r=r;
     if(l==r)
     {
        t[x].v=t[x].a=t[x].zm=t[x].ym=li[l];
        t[x].al=t[x].ar=t[x].zj=t[x].yj=l;
        return;
     }
     int mid=(l+r)>>1,ls=x<<1,rs=(x<<1)|1;
     build(ls,l,mid),build(rs,mid+1,r);
     t[x].v=t[ls].v+t[rs].v;
     int temp=t[ls].ym+t[rs].zm;
     if(t[ls].a>=t[x].a)
        t[x].a=t[ls].a,t[x].al=t[ls].al,t[x].ar=t[ls].ar;
     if(temp>t[x].a)
         t[x].a=temp,t[x].al=t[ls].yj,t[x].ar=t[rs].zj;
     if(t[rs].a>t[x].a)   
        t[x].a=t[rs].a,t[x].al=t[rs].al,t[x].ar=t[rs].ar;
     t[x].zm=t[ls].zm,t[x].zj=t[ls].zj;
     t[x].ym=t[rs].ym,t[x].yj=t[rs].yj;
     if(t[ls].ym+t[rs].v>t[x].ym)
          t[x].ym=t[ls].ym+t[rs].v,t[x].yj=t[ls].yj;
     if(t[rs].zm+t[ls].v>=t[x].zm)
          t[x].zm=t[rs].zm+t[ls].v,t[x].zj=t[rs].zj;
}
int query(int x,int y,int z)
{
    if(y<=t[x].l&&z>=t[x].r)
    {
        zb=t[x].al,yb=t[x].ar;
        zi=t[x].zm,yi=t[x].ym;
        zj=t[x].zj,yj=t[x].yj;
        vi=t[x].v;
        return t[x].a;
    }
    int mid=(t[x].l+t[x].r)>>1;
    if(z<=mid) return query(x<<1,y,z);
    if(y>mid)  return query((x<<1)|1,y,z);
    int ans1=query(x<<1,y,z);
    int jlz1=zi,jly1=yi,ljz1=zj,ljy1=yj,l11=zb,l12=yb,zv1=vi;
    int ans2=query((x<<1)|1,y,z);
    int jlz2=zi,jly2=yi,ljz2=zj,ljy2=yj,l21=zb,l22=yb,zv2=vi;
    int ansz=jly1+jlz2;
    zi=jlz1,zj=ljz1,yi=jly2,yj=ljy2;
    if(jlz2+zv1>zi)  zi=jlz2+zv1,zj=ljz2;
    if(jly1+zv2>=yi)  yi=jly1+zv2,yj=ljy1;
    if(ans1>=ans2&&ans1>=ansz)
    {
        zb=l11,yb=l12;
        return ans1;
    }
    if(ansz>=ans2)
    {
        zb=ljy1,yb=ljz2;
        return ansz;
    }
    zb=l21,yb=l22;
    return ans2;
}
int main()
{
    memset(t,-0x7f,sizeof(t));
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",&li[i]);
    build(1,1,n);
    int a1,a2;
    for(int i=1;i<=m;i++)
    {
       scanf("%d%d",&a1,&a2);
       ans=query(1,a1,a2);
       while(li[yb]==0) yb--;
       printf("%d %d %d\n",zb,yb,ans);
    }
    return 0;
}
hill

 

 

 

【题解】

        当时难得要死,现在一遍打过……比起山海经,简单之处在于权值全部变为0和1,复杂之处在于加了修改操作(需要用到延迟标记)并且询问和一般线段树有所不同。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int sj=50010;
int n,m,a1,a2,a3,zd,lazy[sj*4];
struct B
{
    int z,y,yj,a,l,r,al;
}t[sj*4];
void build(int x,int l,int r)
{
     t[x].l=t[x].al=t[x].yj=l;
     t[x].r=r;
     if(l==r)
     {
        t[x].z=t[x].y=t[x].a=1;
        return;
     }
     int mid=(l+r)>>1,ls=x<<1,rs=(x<<1)|1;
     build(ls,l,mid),build(rs,mid+1,r);
     t[x].a=t[x].z=t[x].y=t[ls].a+t[rs].a;
}
void pushdown(int x)
{
     if(lazy[x])
     {
        int ls=x<<1,rs=(x<<1)|1;
        if(lazy[x]==1) 
        {
           t[rs].a=t[rs].z=t[rs].y=t[rs].r-t[rs].l+1;
           t[ls].a=t[ls].z=t[ls].y=t[ls].r-t[ls].l+1;
           t[rs].al=t[rs].yj=t[rs].l;
           t[ls].al=t[ls].yj=t[ls].l;
           lazy[x]=0,lazy[rs]=lazy[ls]=1;
        }
        if(lazy[x]==-1)
        {
           t[rs].a=t[rs].z=t[rs].y=0;
           t[ls].a=t[ls].z=t[ls].y=0;
           t[rs].al=t[rs].yj=t[rs].r+1;
           t[ls].al=t[ls].yj=t[ls].r+1;
           lazy[x]=0,lazy[rs]=lazy[ls]=-1;
        }
     }
}
int query(int x,int y)
{
     int ls=x<<1,rs=(x<<1)|1;
     if(t[ls].a==0&&t[rs].a==0)  return t[x].l;
     pushdown(x);
     if(t[ls].a>=y) return query(ls,y);
     if(t[ls].y+t[rs].z>=y) return t[ls].yj;
     return query(rs,y);
}
void update(int x,int z,int y,int op)
{
     if(z<=t[x].l&&y>=t[x].r)
     {
        t[x].a=t[x].z=t[x].y=(y-z+1)*op;
        if(op==0)  t[x].al=t[x].yj=t[x].r+1,lazy[x]=-1;
        else       t[x].al=t[x].yj=t[x].l,lazy[x]=1;
        return;
     }
     pushdown(x);
     int mid=(t[x].l+t[x].r)>>1,ls=x<<1,rs=(x<<1)|1;
     if(mid<z) update(rs,z,y,op);
     if(y<=mid) update(ls,z,y,op);
     if(z<=mid&&y>mid)
     {
        update(ls,z,mid,op);
        update(rs,mid+1,y,op);
     }
     t[x].z=t[ls].z,t[x].y=t[rs].y,t[x].yj=t[rs].yj;
     if(t[rs].yj==t[rs].l)  
         t[x].y=t[rs].y+t[ls].y,t[x].yj=t[ls].yj;
     if(t[ls].z==t[ls].r-t[ls].l+1) t[x].z=t[rs].z+t[ls].z;
     t[x].a=t[ls].a,t[x].al=t[ls].al;
     if(t[ls].y+t[rs].z>t[x].a) 
         t[x].a=t[ls].y+t[rs].z,t[x].al=t[ls].yj;
     if(t[rs].a>t[x].a)
         t[x].a=t[rs].a,t[x].al=t[rs].al;
}
int main()
{
    scanf("%d%d",&n,&m);
    build(1,1,n);
    for(int i=1;i<=m;i++)
    {
       scanf("%d%d",&a1,&a2);
       if(a1==1)
       {
          if(t[1].a<a2) printf("0\n");
          else 
          {
             zd=query(1,a2);
             printf("%d\n",zd);
             update(1,zd,zd+a2-1,0);
          }
       }
       if(a1==2)
       {
          scanf("%d",&a3);
          update(1,a2,a2+a3-1,1);
       }
    }
    return 0;
}
hotel

 

KC采花

Description

KC在公园里种了一排花,一共有n朵,游手好闲的KC常常在公园里采花。他对每朵花都有一个美丽度鉴赏。由于对花的喜好不同,有的花分数很高,有的花分数很低,甚至会是负数。
KC很忙,每次采花的时候,不可能从第一朵花,走到第n朵。所以他会先选定一个区间[l,r](1<=l<=r<=n),作为当天的采花范围。同时为了方便采花,他总是从[l,r]中最多选出k个互不相交的子区间,将这些子区间的花全部采光。当然,他希望美丽度总和最大。
KC对花的鉴赏随着他对世界观人生观的改变而改变,他会不时地对每朵花的美丽度进行修改,可能改低,也可能提高。
KC的行为持续m天,每天的行为要么是采花,要么是改变花的美丽度。
注:(1)[l,r]的最多k个互不相同子区间可以表示成:[x1,y1],[x2,y2],...,[xt,yt],满足l<=x1<=y1<x2<=y2<...<xt<=yt<=r,且0<=t<=k。
(2)由于是KC种的花,一朵花采掉第二天会立刻生出来。

Input

第一行一个正整数n,n<=100000。
第二行n个整数a1,a2...an,表示n朵花的美丽度。|ai|<=500。
第三行一个正整数m,m<=100000。
第四行开始,接下来m行,每行表示该天KC的行为。
修改美丽度的行为用0 i val描述,表示将ai修改为val,|val|<=500。
采花行为用1 l r k描述,k<=20意义如题面。

Output

对于每个采花行为,每行一个整数表示最大的美丽度总和。

Sample Input

9
9 -8 9 -1 -1 -1 9 -8 9
3
1 1 9 1
1 1 9 2
1 4 6 3

Sample Output

17
25
0

HINT

100%的数据,满足n,m<=50000,k<=20,ai以及修改的val的绝对值不超过500。

BZOJ 3267/3638/3272/2288四倍经验……3502题面和2288一样但是数据是10^6卡内存需要堆贪心

 

题解

      选取k个不连续区间使权值和最大,理所当然的思路是用网络流建图跑费用流。但是以这种数据范围来看TLE是一定的,这就用到了线段树。线段树中维护的是区间最大值和最小值,当我们选中一个区间的最大值就相当于网络流当中选了一条路径,类似退流地交换最大值和最小值,区间内选择至多m次就满足了题目的要求。

       因为询问时需要区间翻转,所以仍然要记录最大值的起止点。在山海经中维护得很复杂的种种信息在这份代码里统一用结构体处理,瞬间清爽了不少,思路也更清楚了。因为需要一边询问一边翻转,应该记录翻转过的区间,询问过后再翻回来。至于翻转的lazy操作就简单多了,一个bool来回来去异或就可以胜任。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<stack>
using namespace std;
const int sj=100010;
int n,a[sj],m,a1,a2,a3,ans;
bool op;
struct node
{
    int pl,pr,p1,p2,lx,rx,mx,sum;
    void add(int pos,int x)
    {
       p1=p2=pl=pr=pos;
       lx=rx=mx=sum=x;
    }
}tmp;
stack<node> q;
struct tree
{
    int l,r;
    bool flag;
    node ma,mi;
    void add(int x)
    {
       ma.add(l,x);
       mi.add(l,-x);
    }
}t[sj*4];
node merge(node ls,node rs)
{
     node rt;
     rt.sum=rs.sum+ls.sum;
     rt.lx=ls.sum+rs.lx > ls.lx ? ls.sum+rs.lx : ls.lx;
     rt.pl=ls.sum+rs.lx > ls.lx ? rs.pl : ls.pl;
     rt.rx=rs.sum+ls.rx > rs.rx ? rs.sum+ls.rx : rs.rx;
     rt.pr=rs.sum+ls.rx > rs.rx ? ls.pr : rs.pr;
     rt.mx=rs.lx+ls.rx,rt.p1=ls.pr,rt.p2=rs.pl;
     if(rt.mx<rs.mx)  rt.mx=rs.mx,rt.p1=rs.p1,rt.p2=rs.p2;
     if(rt.mx<ls.mx)  rt.mx=ls.mx,rt.p1=ls.p1,rt.p2=ls.p2;
     return rt;
}
void pushdown(int x)
{
     if(!t[x].flag||t[x].l==t[x].r) return;
     t[x].flag=0;
     swap(t[x<<1].mi,t[x<<1].ma);
     swap(t[x<<1|1].mi,t[x<<1|1].ma);
     t[x<<1].flag^=1,t[x<<1|1].flag^=1;
}
void pushup(int x)
{
     t[x].mi=merge(t[x<<1].mi,t[x<<1|1].mi);
     t[x].ma=merge(t[x<<1].ma,t[x<<1|1].ma);
}
void build(int x,int z,int y)
{
     t[x].l=z,t[x].r=y;
     if(z==y)
     {
        t[x].add(a[z]);
        return;
     }
     int mid=(z+y)>>1;
     build(x<<1,z,mid),build(x<<1|1,mid+1,y);
     pushup(x);
}
node query(int x,int z,int y)
{
     pushdown(x);
     if(t[x].l==z&&t[x].r==y)  return t[x].ma;
     int mid=(t[x].l+t[x].r)>>1;
     if(y<=mid)  return query(x<<1,z,y);
     if(z>mid)   return query(x<<1|1,z,y);
     return merge(query(x<<1,z,mid),query(x<<1|1,mid+1,y)); 
}
void reverse(int x,int z,int y)
{
     pushdown(x);
     if(z<=t[x].l&&t[x].r<=y)
     {
        swap(t[x].ma,t[x].mi);
        t[x].flag^=1;
        return;
     }
     int mid=(t[x].l+t[x].r)>>1;
     if(z<=mid)  reverse(x<<1,z,y);
     if(y>mid)   reverse(x<<1|1,z,y);
     pushup(x);
}
void update(int x,int z)
{
     pushdown(x);
     if(t[x].l==t[x].r&&t[x].l==z)
     {
        t[x].add(a[z]);
        return;
     }
     int mid=(t[x].l+t[x].r)>>1;
     if(z<=mid)  update(x<<1,z);
     else      update(x<<1|1,z);
     pushup(x);
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)  scanf("%d",&a[i]);
    build(1,1,n);
    scanf("%d",&m);
    for(int i=1;i<=m;i++)
    {
       scanf("%d%d%d",&op,&a1,&a2);
       if(op)
       {
          scanf("%d",&a3);
          ans=0;
          for(int j=1;j<=a3;j++)
          {
             tmp=query(1,a1,a2);
             if(tmp.mx>0)  ans+=tmp.mx;
             else break;
             reverse(1,tmp.p1,tmp.p2);
             q.push(tmp);
          }
          while(!q.empty())
          {
             reverse(1,q.top().p1,q.top().p2);
             q.pop();
          }
          printf("%d\n",ans);
       }
       else
       {
          a[a1]=a2;
          update(1,a1);
       }
    }
    return 0;
}
flower

 

[HNOI2012]永无乡

时间限制: 1 Sec  内存限制: 128 MB

题目描述

永无乡包含 n 座岛,编号从 1 到 n,每座岛都有自己的独一无二的重要度,按照重要度可 以将这 n 座岛排名,名次用 1 到 n 来表示。某些岛之间由巨大的桥连接,通过桥可以从一个岛 到达另一个岛。如果从岛 a 出发经过若干座(含 0 座)桥可以到达岛 b,则称岛 a 和岛 b 是连 通的。现在有两种操作:B x y 表示在岛 x 与岛 y 之间修建一座新桥。Q x k 表示询问当前与岛 x连通的所有岛中第 k 重要的是哪座岛,即所有与岛 x 连通的岛中重要度排名第 k 小的岛是哪 座,请你输出那个岛的编号。 

输入

输入文件第一行是用空格隔开的两个正整数 n 和 m,分别 表示岛的个数以及一开始存在的桥数。接下来的一行是用空格隔开的 n 个数,依次描述从岛 1 到岛 n 的重要度排名。随后的 m 行每行是用空格隔开的两个正整数 ai 和 bi,表示一开始就存 在一座连接岛 ai 和岛 bi 的桥。后面剩下的部分描述操作,该部分的第一行是一个正整数 q, 表示一共有 q 个操作,接下来的 q 行依次描述每个操作,操作的格式如上所述,以大写字母 Q 或B 开始,后面跟两个不超过 n 的正整数,字母与数字以及两个数字之间用空格隔开。 对于 20%的数据 n≤1000,q≤1000 
 
对于 100%的数据 n≤100000,m≤n,q≤300000 

输出

对于每个 Q x k 操作都要依次输出一行,其中包含一个整数,表 示所询问岛屿的编号。如果该岛屿不存在,则输出-1。 

样例输入

5  1           
4  3 2 5 1        
1  2           
7
Q 3 2           
Q 2 1 
B 2 3 
B 1 5 
Q 2 1 
Q 2 4 
Q 2 3 

样例输出

-1
2
5
1
2

题解
权值线段树+线段树合并……拿下标当作建树依据这种事是做过的,但是这道题其实根本就没有按传统方法建树。动态开点,直接在一开始分配了一个内存池。之后就是对于每一个结点初步建一棵线段树,线段树表示的范围都是由1到n,来统计各个权值的点的个数。因为表示的范围相同,线段树可以按照代码中所写的方式进行简单粗暴的合并,然后像平衡树一样查询区间第k大。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<ctime>
using namespace std;
const int sj=100010;
int n,f[sj],m,a[sj],a1,a2,cnt,root[sj],id[sj];
int ls[sj*18],rs[sj*18],sum[sj*18];
char hn;
int find(int x)
{
    if(f[x]==-1)  return x;
    f[x]=find(f[x]);
    return f[x];
}
void hb(int x,int y)
{
    x=find(x),y=find(y);
    if(x!=y)  f[x]=y;
}
void insert(int &k,int l,int r,int val)
{
    if(!k)  k=++cnt;
    if(l==r)
    {
        sum[k]=1;
        return;
    }
    int mid=(l+r)>>1;
    if(val<=mid)  insert(ls[k],l,mid,val);
    else insert(rs[k],mid+1,r,val);
    sum[k]=sum[ls[k]]+sum[rs[k]];
}
int query(int k,int l,int r,int rank)
{
    if(l==r)  return l;
    int mid=(l+r)>>1;
    if(sum[ls[k]]>=rank)  return query(ls[k],l,mid,rank);
    else return query(rs[k],mid+1,r,rank-sum[ls[k]]);
}
int merge(int x,int y)
{
    if(!x)  return y;
    if(!y)  return x;
    ls[x]=merge(ls[x],ls[y]);
    rs[x]=merge(rs[x],rs[y]);
    sum[x]=sum[ls[x]]+sum[rs[x]];
    return x;
}
int main()
{
    scanf("%d%d",&n,&m);
    memset(f,-1,sizeof(f));
    for(int i=1;i<=n;i++)    scanf("%d",&a[i]);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&a1,&a2);
        hb(a1,a2);
    }
    for(int i=1;i<=n;i++)
    {
        insert(root[find(i)],1,n,a[i]);
        id[a[i]]=i;
    }
    scanf("%d",&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%s%d%d",&hn,&a1,&a2);
        if(hn=='Q')
        {
            if(sum[root[find(a1)]]<a2)
            {
                printf("-1\n");
                continue;
            }
            int t=query(root[find(a1)],1,n,a2);
            printf("%d\n",id[t]);
        }
        else
        {
            if(find(a1)==find(a2))  continue;
            root[find(a2)]=merge(root[find(a1)],root[find(a2)]);
            hb(a1,a2);
        }
    }
    return 0;
}
merge

 

[HEOI2013]Segment

时间限制: 4 Sec  内存限制: 256 MB

题目描述

要求在平面直角坐标系下维护两个操作: 
1.在平面上加入一条线段。记第i条被插入的线段的标号为i。 
2.给定一个数k,询问与直线 x = k相交的线段中,交点最靠上的线段的编号。  

输入

第一行一个整数n,表示共n 个操作。 
接下来n行,每行第一个数为0或1。 
 
若该数为 0,则后面跟着一个正整数 k,表示询问与直线  
x = ((k +lastans–1)%39989+1)相交的线段中交点(包括在端点相交的情形)最靠上的线段的编号,其中%表示取余。若某条线段为直线的一部分,则视作直线与线段交于该线段y坐标最大处。若有多条线段符合要求,输出编号最小的线段的编号。 
若该数为 1,则后面跟着四个正整数 x0, y0, x 1, y 1,表示插入一条两个端点为 
((x0+lastans-1)%39989+1,(y0+lastans-1)%10^9+1)和((x
1+lastans-1)%39989+1,(y1+lastans-1)%10^9+1) 的线段。 
其中lastans为上一次询问的答案。初始时lastans=0。 

输出

对于每个 0操作,输出一行,包含一个正整数,表示交点最靠上的线段的编号。若不存在与直线相交的线段,答案为0。 

样例输入

6 
1 8 5 10 8
1 6 7 2 6
0 2
0 9
1 4 7 6 7
0 5 

样例输出

2 
0 3   

提示

对于100%的数据,1 ≤ n ≤ 10^5 , 1 ≤  k, x0, x1 ≤ 39989, 1 ≤ y0 ≤ y1 ≤ 10^9。

 

题解

      一种新的线段树:李超线段树,特点是标记永久化。用途,主要就是本题叙述的,线段最高点的模型。对于每一个标记,如果不是全部被更新就不修改本区间的标记,而是在子区间更新最优值;查询时则每次从上到下查询每一个区间的标记,复杂度logn。具体细节在代码里注释得很清楚了。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#define eps 1e-12
using namespace std;
const int mod1=39989;
const int mod2=1e9;
const int sj=100005;
int n,op,la,tot,node[sj*4],poi[sj];
//node实际上是线段树,因为只需要维护标记所以用数组表示 
inline int read()
{
    int jg=0,jk=getchar()-'0';
    if(jk>0&&jk<=9)  jg=jk;
    jk=getchar()-'0';
    while(jk>=0&&jk<=9)  jg*=10,jg+=jk,jk=getchar()-'0';
    return jg;
}
struct line
{
    int l,r;
    double k,b;
    line(int xa=0,int xb=0,int ya=0,int yb=0)
    {
       l=xa,r=xb;
       if(xa!=xb)
       {
          k=1.0*(yb-ya)/(xb-xa);
          b=ya-k*xa;
       }
       else  k=0,b=max(ya,yb);
    }//求斜率和截距 
    double f(int x)
    {
       return x*k+b;
    }//求某一横坐标对应的纵坐标 
}a[sj];
inline int sgn(double x)
{
     return (x>-eps)-(x<eps);
}//double型判正负 
int cross(int x,int y)
{
    return floor(a[x].b-a[y].b)/(a[y].k-a[x].k);
}//计算两直线交点对应的横坐标 
int calc(int x,int y,int pos)
{
    if(!x&&!y)  return 0;
    if(!x)      return y;
    if(!y)      return x;
    double xx=a[x].f(pos),yy=a[y].f(pos);
    int flag=sgn(xx-yy);
    if(flag==0)  return x<y?x:y;
    return flag>0?x:y;
}//判断某一横坐标(pos)上x和y两条线段哪一个更优 
void update(int pos,int idx)
{
     if(!poi[pos])   poi[pos]=idx;
     else
     {
         double x=a[idx].f(pos),y=a[poi[pos]].f(pos);
         int flag=sgn(x-y);
         if(flag>0||(flag==0&&idx<poi[pos]))   poi[pos]=idx;
     }
}//对于线段端点的特殊处理,即使在区间两侧都不优,至少端点处的值或许可以更新 
void insert(int l,int r,int rt,int le,int ri,int x)
{
     if(l==le&&r==ri)
     {
        if(!node[rt])  node[rt]=x;
        else
        {
            bool L=sgn(a[x].f(l)-a[node[rt]].f(l))>0,R=sgn(a[x].f(r)-a[node[rt]].f(r))>0;
            //L:在区间左端点是否优;R:在区间右端点是否优 
            if(L&&R)  node[rt]=x;
            else if(L||R)
            {
                int mid=(l+r)>>1,tar=cross(x,node[rt]); 
                if(tar<=mid&&L)  insert(l,mid,rt<<1,le,mid,x);
                if(tar<=mid&&R)  insert(l,mid,rt<<1,le,mid,node[rt]),node[rt]=x;
                if(tar>mid&&L)   insert(mid+1,r,rt<<1|1,mid+1,ri,node[rt]),node[rt]=x;
                if(tar>mid&&R)   insert(mid+1,r,rt<<1|1,mid+1,ri,x);
            }//不管新添加的节点是否优,左右两个下层区间都会有标记;标记为x或下传原父节点标记 
            else update(l,x),update(r,x);
        }//整个区间都更优才会修改区间标记,否则修改下层标记 
        return;
     }
     int mid=(l+r)>>1;
     if(ri<=mid)      insert(l,mid,rt<<1,le,ri,x);
     else if(le>mid)  insert(mid+1,r,rt<<1|1,le,ri,x);
     else             insert(l,mid,rt<<1,le,mid,x),insert(mid+1,r,rt<<1|1,mid+1,ri,x);
}//添加一条线段后维护线段树 
int query(int l,int r,int rt,int pos)
{
    int res=node[rt];
    if(l==r)  return res;
    int mid=(l+r)>>1;
    if(pos<=mid)  return calc(res,query(l,mid,rt<<1,pos),pos);
    else          return calc(res,query(mid+1,r,rt<<1|1,pos),pos);
}//询问某一点的最优线段,从上到下不断取max,logn 
int main()
{
    n=read();
    for(int l=1;l<=n;l++)
    {
       op=read();
       if(op)
       {
          int xa=(read()+la-1)%mod1+1,ya=(read()+la-1)%mod2+1,xb=(read()+la-1)%mod1+1,yb=(read()+la-1)%mod2+1;
          if(xa>xb)  swap(xa,xb),swap(ya,yb);
          a[++tot]=line(xa,xb,ya,yb);
          insert(1,mod1,1,xa,xb,tot);
       }
       else
       {
          int x=(read()+la-1)%mod1+1;
          la=query(1,mod1,1,x);
          if(poi[x])  if(sgn(a[poi[x]].f(x)-a[la].f(x))==0&&poi[x]<la)  la=poi[x];
          printf("%d\n",la);
       }
    }
    return 0;
} 
segment

 

 

 一篇与线段树有关的好文章:ORZMike

 
posted @ 2017-08-05 12:16  moyiii  阅读(446)  评论(0编辑  收藏  举报