线段树专题

1.hud 1166 http://acm.hdu.edu.cn/showproblem.php?pid=1166    敌兵布阵

  本题目涉及到了区间操作和点操作两种操作。

  区间操作(区间和):我们采用了递推的方法来建树,建好了叶子结点之后,我们再回溯来建整个区间。(这个操作是关键)

  点操作:点操作必然涉及到区间的改动,我们判定,只要这个点在一个区间上,那么我们就对这个区间操作。

View Code
#include "iostream"

#include "string"

#include "algorithm"

using namespace std;

#define maxn 50005

int len, peo[maxn], ans;

typedef struct node

{

    int l, r, m;

    int s;

}node;

node t[maxn*4];

void build(int ll, int le, int ri)

{

    t[ll].l = le;

    t[ll].r = ri;

    t[ll].m = (le+ri)>>1;

    if(le==ri) t[ll].s = peo[le];

    else

    {

        build(ll<<1, le, t[ll].m);

        build(ll<<1|1, t[ll].m+1, ri);

        t[ll].s = t[ll<<1].s + t[ll<<1|1].s;

    }

}

void query(int ll, int le, int ri)

{

    if(le<=t[ll].l && ri>=t[ll].r) ans+=t[ll].s;

    else

    {

        if(le>t[ll].m) query(ll<<1|1, le, ri);

        else if(ri<=t[ll].m) query(ll<<1, le, ri);

        else 

        {

            query(ll<<1, le, t[ll].m);

            query(ll<<1|1, t[ll].m+1, ri);

        }

    }

}

void add(int ll, int p, int v)

{

    if(p>=t[ll].l && p<=t[ll].r) t[ll].s += v;

    if(t[ll].l==p && t[ll].r==p) return;

    if(p>t[ll].m) add(ll<<1|1, p, v);

    else if(p<=t[ll].m) add(ll<<1, p, v);

}

void sub(int ll, int p, int v)

{

    if(p>=t[ll].l && p<=t[ll].r) t[ll].s -= v;

    if(t[ll].l==p && t[ll].r==p) return;

    if(p>t[ll].m) sub(ll<<1|1, p, v);

    else if(p<=t[ll].m) sub(ll<<1, p, v);

}

int main()

{

    int i, j, C, le, ri;

    char op[10];

    scanf("%d", &C);

    for(i=1; i<=C; i++)

    {

        printf("Case %d:\n", i);

        scanf("%d", &len);

        for(j=1; j<=len; j++) scanf("%d", peo+j);

        

        build(1, 1, len);



        while(scanf("%s", op)!=EOF)

        {

            if(strcmp(op, "End")==0) break;

            scanf("%d%d", &le, &ri);

            if(strcmp(op, "Query")==0)

            {

                ans = 0;

                query(1, le, ri);

                printf("%d\n", ans);

            }

            else if(strcmp(op, "Add")==0)

            {

                add(1, le, ri);

            }

            else if(strcmp(op, "Sub")==0)

            {

                sub(1, le, ri);

            }

        }

    }

    return 0;

}

 

2.hdu 1754 http://acm.hdu.edu.cn/showproblem.php?pid=1754     I hate it

  本题目涉及到了区间操作和点操作两种操作。

  区间操作(区间最值):平常的建树。

  点操作:我们每向区间插入一个点就和当前区间的最大值比较一下,如果大于最大值,则更新。(这个操作是关键)

View Code
#include "iostream"

#include "string"

#include "cstdio"

#include "cstring"

#include "algorithm"

using namespace std;

#define maxn 200005

typedef struct node

{

    int l, r, m, ma;

}node;

int ans;

node t[maxn<<2];

void Build(int ll, int l, int r)

{

    t[ll].ma=-1;

    t[ll].l=l;

    t[ll].r=r;

    t[ll].m=(l+r)>>1;

    if(l!=r)

    {

        Build(ll<<1, l, t[ll].m);

        Build(ll<<1|1, t[ll].m+1, r);

    }

}

void Insert(int ll, int p, int v)

{

    if(p>=t[ll].l && p<=t[ll].r && t[ll].ma<v) t[ll].ma=v;

    if(p==t[ll].l && p==t[ll].r) return;

    if(p<=t[ll].m) Insert(ll<<1, p, v);

    else if(p>t[ll].m) Insert(ll<<1|1, p, v);

}

int Find(int ll, int l, int r)

{

    if(l==t[ll].l && t[ll].r==r) return t[ll].ma; 

    if(r<=t[ll].m) Find(ll<<1, l, r);

    else if(l>t[ll].m) Find(ll<<1|1, l, r);

    else

    {

        int a=Find(ll<<1, l, t[ll].m);

        int b=Find(ll<<1|1, t[ll].m+1, r);

        return a>b?a:b;

    }

}

int main()

{

    int n, op, i, j, v, l, r;

    char opp[10];

    while(scanf("%d%d", &n, &op)!=EOF)

    {

        Build(1, 1, n);

        for(i=1; i<=n; i++)

        {

            scanf("%d", &v);

            Insert(1, i, v);

        }

        for(i=0; i<op; i++)

        {

            scanf("%s%d%d", opp, &l, &r);

            if(strcmp(opp, "Q")==0) 

            {

                

                printf("%d\n", Find(1, l, r));

            }

            else if(strcmp(opp, "U")==0)

            {

                Insert(1, l, r);

            }

        }

    }

}

 

3.hdu 1698http://acm.hdu.edu.cn/showproblem.php?pid=1698    Just a hook

  本题只涉及到了区间操作。

  区间操作(全区间键值的改变):在维护更新的时候我们将无用的区间屏蔽掉,这样就可以大大提高时间效率。如何屏蔽?这个是关键。

View Code
#include "iostream"

#include "string"

#include "cstdio"

#include "cstring"

#include "algorithm"

using namespace std;

#define maxn 100005

#define L(x) (x<<1)

#define R(x) (x<<1|1)

typedef struct node

{

    int l, r, z;

}node;

node t[maxn<<2];

void Build(int ll, int l, int r)

{

    t[ll].l=l;

    t[ll].r=r;

    t[ll].z=1;

    if(l<r)

    {

        int m=(t[ll].l+t[ll].r)>>1;

        Build(L(ll), l, m);

        Build(R(ll), m+1, r);

    }

}

void Update(int ll, int l, int r, int z)

{

    if(l==t[ll].l && t[ll].r==r) 

    {

        t[ll].z=z;//更新

        return;

    }

    if(t[ll].z>0)//只要不是刚好的区间,我们就往下更新,同时将这一层的区间屏蔽掉,说具体点就是t[ll].z=-1

    {

        t[R(ll)].z = t[ll].z;

        t[L(ll)].z = t[ll].z;

        t[ll].z=-1;

    }

    if(t[ll].l==t[ll].r) return;

    int m=(t[ll].l+t[ll].r)>>1;

    if(r<=m) Update(L(ll), l, r, z);

    else if(l>m) Update(R(ll), l, r, z);

    else 

    {

        Update(L(ll), l, m, z);

        Update(R(ll), m+1, r, z);

    }

}

int Query(int ll, int l, int r)

{

    if(t[ll].z>0) return (t[ll].r-t[ll].l+1)*t[ll].z;

    int m=(t[ll].l+t[ll].r)>>1;

    if(r<=m) return Query(L(ll), l, r);

    else if(l>m) return Query(R(ll), l, r);

    else return Query(L(ll), l, m)+Query(R(ll), m+1, r);

}

int main()

{

    int c, n, op, i, l, r, z, j, ans;

    scanf("%d", &c);

    for(i=1; i<=c; i++)

    {

        scanf("%d%d", &n, &op);

        Build(1, 1, n);

        for(j=0; j<op; j++)

        {

            scanf("%d%d%d", &l, &r, &z);

            Update(1, l, r, z);

        }

        ans=Query(1, 1, n);

        printf("Case %d: The total value of the hook is %d.\n", i, ans);

    }

}

 

4.hdu 1394http://acm.hdu.edu.cn/showproblem.php?pid=1394    Minimum Inversion Number

本题是一种类型的题目——逆序数的求法。

本题涉及到了区间操作。

但是本题的区间操作有一定的讲究,也就是一边插入一边查找。不然等插入完了再来查找就不知道是在它前面还是后面出现的了。

其它的就和平常的线段树一样了。

View Code
#include "iostream"

#include "string"

#include "cstring"

#include "algorithm"

#include "cstdio"

using namespace std;

#define maxn 5005

#define L(x) (x<<1)

#define R(x) (x<<1|1)

typedef struct segtree

{

    int l, r, s;

}segtree;

segtree t[maxn<<2];

int s[maxn];

void Build(int ll, int l, int r)

{

    t[ll].l=l;

    t[ll].r=r;

    t[ll].s=0;

    if(l<r)

    {

        int m=(l+r)>>1;

        Build(L(ll), l, m);

        Build(R(ll), m+1, r);

    }

}

int Query(int ll, int l, int r)

{

    if(t[ll].l==l && t[ll].r==r) return t[ll].s;

    int m=(t[ll].l+t[ll].r)>>1;

    if(r<=m) return Query(L(ll), l, r);

    else if(l>m) return Query(R(ll), l, r);

    else return Query(L(ll), l, m)+Query(R(ll), m+1, r);

}

void Update(int ll, int k)

{

    t[ll].s++;

    if(t[ll].l==t[ll].r) return;

    int m=(t[ll].l+t[ll].r)>>1;

    if(k<=m) Update(L(ll), k);

    else Update(R(ll), k);

}

int main()

{

    int n, i, t, ans;

    while(scanf("%d", &n)!=EOF)

    {

        ans=0;

        Build(1, 0, n-1);

        for(i=0; i<n; i++)//我们来求一下“输入顺序时”的逆序对数

        {

            scanf("%d", &s[i]);

            ans+=Query(1, s[i], n-1);//在s[i]~n-1这个区间中有多少个数是在s[i]之前出现的,那么对于这个数而言就有多少个逆序对数,不是吗?好好想想……

            Update(1, s[i]);

        }

        t=ans;

        for(i=0; i<n-1; i++)

        {

            t=t+(n-1-s[i])-s[i];

            if(ans>t) ans=t;

        }

        printf("%d\n", ans);

    }

    return 0;

}

 

5. hdu 2492http://acm.hdu.edu.cn/showproblem.php?pid=2492    Ping pong

本题也是一道逆序数的题目。只不为要求两种逆序都要算出来。也就是说A>B>C和A<B<C两种情况。

其实仔细想想本题我们可以得到一个这样的结论。

如果完全按照题目的描述来想算法,我们是很容易进入误区的,

但是,如果我们将题目的意思也取反来看一下,当然这个取反和原题意是等价的,就很容易知道是什么样的题目了。

View Code
#include "iostream"

#include "string"

#include "cstring"

#include "algorithm"

#include "cstdio"

using namespace std;

#define maxn 20010

#define maxs 100010

#define L(x) (x<<1)

#define R(x) (x<<1|1)

typedef struct segtree

{

    int l, r, s;

}segtree;

segtree t[maxs<<2];

int s[maxn];

void Build(int f, int l, int r)

{

    t[f].l=l;

    t[f].r=r;

    t[f].s=0;

    if(l<r)

    {

        int m=(l+r)>>1;

        Build(L(f), l, m);

        Build(R(f), m+1, r);

    }

}

int Query(int f, int l, int r)

{

    if(t[f].l==l && t[f].r==r) return t[f].s;

    int m=(t[f].l+t[f].r)>>1;

    if(r<=m) return Query(L(f), l, r);

    else if(l>m) return Query(R(f), l, r);

    else return Query(L(f), l, m)+Query(R(f), m+1, r);

}

void Update(int f, int k)

{

    t[f].s++;

    if(t[f].l==t[f].r) return;

    int m=(t[f].l+t[f].r)>>1;

    if(k<=m) Update(L(f), k);

    else Update(R(f), k);

}

int main()

{

    int n, i, T, k;

    long long fs[maxn], fb[maxn], ss[maxn], sb[maxn]; 

    scanf("%d", &T);

    for(k=0; k<T; k++)

    {

        scanf("%d", &n);

        memset(fs, 0, sizeof fs);

        memset(fb, 0, sizeof fb);

        memset(ss, 0, sizeof ss);

        memset(sb, 0, sizeof sb);

        Build(1, 1, maxs);

        for(i=0; i<n; i++)//我们来求一下“输入顺序时”的逆序对数

        {

            scanf("%d", &s[i]);

            fb[i]=Query(1, s[i]+1, maxs);//big

            if(s[i]==1)

            {

                Update(1, s[i]);

                continue;

            }

            fs[i]=Query(1, 1, s[i]-1);//在s[i]~n-1这个区间中有多少个数是在s[i]之前出现的,那么对于这个数而言就有多少个逆序对数,不是吗?好好想想……

            Update(1, s[i]); 

        }

        Build(1, 1, maxs);

        for(i=n-1; i>=0; i--)

        {

            sb[i]=Query(1, s[i]+1, maxs);

            if(s[i]==1)

            {

                Update(1, s[i]);

                continue;

            }

            ss[i]=Query(1, 1, s[i]-1);//small

            Update(1, s[i]);

        }

        long long ans=0;

        for(i=1; i<n-1; i++)

        {

            ans+=fs[i]*sb[i];

            ans+=fb[i]*ss[i];

        }

        printf("%I64d\n", ans);

    }

    return 0;

}

 

 6. hdu 3308http://acm.hdu.edu.cn/showproblem.php?pid=3308    LCIS

本题是一种类型的题目,解法有一定的套路。也就是lm,rm,m,len,lv,rv等等,具体是什么含义,看看代码就知道了。

本题涉及到了区间操作和点的操作。

区间操作,这个比较麻烦,因为没有记录左右边界,所以要小心,这只是一个不起眼的小难题。

区间的操作,难就难在up函数,和query函数上,这两个一定要看懂。其它的就好说了。

点操作一般,就是平时的操作。

View Code
#include "iostream"

#include "string"

#include "cstdio"

#include "cstring"

#include "algorithm"

using namespace std;

#define L(x) (x<<1)

#define R(x) (x<<1|1)

#define maxn 100005

typedef struct node

{

    int lm, rm, m;

    int lv, rv, len;

}node;//我们并没有记录l, r

node t[maxn<<2];

void Up(node &f, node &l, node &r)

{

    f.lv = l.lv;

    f.rv = r.rv;

    if(l.rv<r.lv)//是递增的

    {

        f.lm=(l.lm==l.len?l.lm+r.lm:l.lm);//左边的最值就是它的区间长度那么……否则……

        f.rm=(r.rm==r.len?r.rm+l.rm:r.rm);//右边的最值就是它的区间长度那么……否则……

        f.m=max(max(f.lm, f.rm), l.rm+r.lm);//l.rm+r.lm表示不包含左右端点的值

        f.m=max(max(l.m, r.m), f.m);

    }

    else//如果不是递增的

    {

        f.lm=l.lm;

        f.rm=r.rm;

        f.m=max(l.m, r.m);

    }

}

void Build(int f, int l, int r)

{

    t[f].len=r-l+1;

    if(l==r)

    {

        scanf("%d", &t[f].lv);

        t[f].rv = t[f].lv;

        t[f].lm = t[f].rm = t[f].m = 1;

        return;

    }

    int m=(l+r)>>1;

    Build(L(f), l, m);

    Build(R(f), m+1, r);

    //当建树完成的时候我们要进行统计运算,也就是向上计算

    Up(t[f], t[L(f)], t[R(f)]);

}

void Update(int f, int l, int r, int p, int v)//我们只需要更新它的lv, rv就可以了

{

    if(l==r)

    {

        t[f].lv = t[f].rv = v;

        return;

    }

    int m=(l+r)>>1;

    if(p<=m) Update(L(f), l, m, p, v);//

    else Update(R(f), m+1, r, p, v);//我们的目的是更新叶子结点,所以不需要更新区间

    //当修改完成的时候我们要进行统计运算,也就是向上计算

    Up(t[f], t[L(f)], t[R(f)]);

}

node Query(int f, int l, int r, int &L, int &R)

{

    if(L<=l && r<=R) return t[f];

    int m=(l+r)>>1;

    node t1, t2;

    t1.len=t2.len=0;

    if(L<=m) t1=Query(L(f), l, m, L, R);//往左可走,就走

    if(R>m) t2=Query(R(f), m+1, r, L, R);//往右可走,就走

    if(t1.len && t2.len)//都找到了,从两边找到了

    {

        node tmp;

        Up(tmp, t1, t2);

        tmp.len=t1.len+t2.len;//哦哦,有可能多次更新,所以不能大意哦!!!

        return tmp;

    }

    if(t1.len) return t1;

    return t2;

}

int main()

{

    int T;

    scanf("%d",&T);

    while(T--)

    {

        int n,m,x,y;

        node te;

        char c;

        scanf("%d%d",&n,&m);

        n--;

        Build(1, 0,n);

        getchar();

        while(m--)

        {

            getchar();

            scanf("%c%d%d",&c,&x,&y);

            if(c=='Q')

            {

                te=Query(1,0,n,x,y);

                printf("%d\n",te.m);

            }

            else Update(1, 0, n, x, y);

        }

    }

    return 0;

}

 

7.hdu 4046http://acm.hdu.edu.cn/showproblem.php?pid=4046    Panda

本题就是一道简单的线段树题目。

但是难就难在只有当我们经过一定的转化之后,才可以用线段树来做,转化的这个过程是比较困难的。

这就是本题的难点所在。转化 转化 转化 转化 再转化

View Code
#include "iostream"

#include "string"

#include "cstdio"

#include "cstring"

#include "algorithm"

using namespace std;

#define L(x) (x<<1)

#define R(x) (x<<1|1)

#define maxn 50010

typedef struct node

{

    int l, r, s;

}node;

node t[maxn<<2];

char str[maxn];

int a[maxn];

void build(int f, int l, int r)

{

    t[f].l = l;

    t[f].r = r;

    if(l==r)

    {

        t[f].s=a[l];

        return;

    }

    int m=(l+r)>>1;

    build(L(f), l, m);

    build(R(f), m+1, r);

    t[f].s=t[L(f)].s+t[R(f)].s;

}

void update(int f, int l, int r, int p, int v)

{

    if(l==r)

    {

        t[f].s=v;

        return;

    }

    int m=(l+r)>>1;

    if(p<=m) update(L(f), l, m, p, v);

    else update(R(f), m+1, r, p, v);

    t[f].s=t[L(f)].s+t[R(f)].s;

}

int Query(int f, int l, int r, int L, int R)

{

    if(L<=l && r<=R) return t[f].s;

    int m=(l+r)>>1;

    int ret=0;

    if(L<=m) ret += Query(L(f), l, m, L, R);

    if(R>m) ret += Query(R(f), m+1, r, L, R);

    return ret;

}

int main()

{

    int cas,T=0,n,m,r,l,c;  

    char st;  

    scanf("%d",&cas);  

    while(cas--)  

    {  

        scanf("%d %d",&n,&m);  

        scanf("%s",str);  

        memset(a,0,sizeof(a));  

        printf("Case %d:\n",++T);  

        for(int i=2;i<n;i++)//标记以该下标结尾的长度为3的子串是否符合要求  

        {  

            if(str[i]!='w')  

                a[i]=0;  

            else if(str[i-1]=='b'&&str[i-2]=='w')  

                a[i]=1;  

        }  

        build(1,0,n);  

        while(m--)  

        {  

            scanf("%d",&c);  

            if(c==0)  

            {  

                scanf("%d %d",&l,&r);  

                l+=2;  

                if(l>r) printf("0\n");  

                else printf("%d\n",Query(1,0,n,l,r));  

            }  

            else  

            {  

                scanf("%d %c",&l,&st);  

                if(str[l]==st)//若要就该的字符跟原先相同,则不用修改了  

                    continue;  

                str[l]=st;  

                if(l>=2&&str[l-2]=='w'&&str[l-1]=='b'&&str[l]=='w')  

                {  

                    if(a[l]==0)//若改之前是不符合要求的串,则更新  

                        update(1, 0,n,l,1);  

                    a[l]=1;  

                }  

                else if(l>=2&&a[l]==1)//若改之前是符合要求的串,则更新  

                {  

                    update(1, 0,n,l,0);  

                    a[l]=0;  

                }  

                if(l>=1&&str[l]=='b'&&str[l-1]=='w'&&str[l+1]=='w')  

                {  

                    if(a[l+1]==0)  

                        update(1, 0,n,l+1,1);  

                    a[l+1]=1;  

                }  

                else if(l<n-1&&l>=1&&a[l+1]==1) a[l+1]=0,update(1, 0,n,l+1,0);  

                if(str[l]=='w'&&str[l+1]=='b'&&str[l+2]=='w')  

                {  

                    if(a[l+2]==0)  

                        update(1, 0,n,l+2,1); 

                    a[l+2]=1;  

                }  

                else if(l<n-2&&a[l+2]==1) a[l+2]=0,update(1, 0,n,l+2,0);   

            }  

        }  

    }  

    return 0;  

}

 

 

你可能感兴趣的:(线段树)