【总结】数据结构:线段树专辑

  首行用来ym hh大牛..Orz

 题目单:http://www.notonlysuccess.com/index.php/segment-tree-complete/

-----------------------------------------------------------------------------------------------

初识线段树 模板题:

hdu1166 敌兵布阵  ,  hdu1754 I hate It (求区间和).

Tips:

  静态线段树内有n个节点通常要把内存开到n*4,才能保证不re.

具体见http://chuanwang66.iteye.com/blog/1428598

初级应用:

hdu1394 Minimum Inversion Number.

  求逆序数的方法: 按照数列的顺序每次访问一个数字x查找区间[1~x)已经插入的

数字的个数,即为逆序数,然后把x插入.而注意到数列第1项为a的话,与a有关的逆序数

x[a]=a-1,于是可以利用这个性质每次循环移位后得到新的逆序数:

inver=inver-x[a]+n-1-x[a].

 

hdu2795 Billboard.

  注意到最小的变量n只有20w,而高度h远大于它,显然h>n的部分是不用考虑的.

uvalive5798 Jupiter Atacks.

http://livearchive.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=3809

  题目大意是给一个b进制,l位数的数字,动态改变一些位的值,询问区间[l,r]的值并模p

例如对10进制的 12345 询问[2,5] 得到一个值2345 输出它模p的值.

  与区间和类似,不同的是得到左区间的l_sum和右区间的r_sum,合并答案时res=l_sum*t+r_sum,而不是直接相加.

这里的t与进制和右区间长度有关.

Jupiter Atacks
#include<stdio.h>
#include<string.h>
#define maxn 100001
typedef long long LL;
LL sum[maxn*4];
long b,p,len,n;
LL pow(LL cur,long times)
{
LL res;
if(times==1)return cur;
res=pow(cur,times>>1);
res=res*res;
if(res>=p)res%=p;
if(times&1)res=res*cur;
if(res>=p)res%=p;
return res;
}
void update(long goal,long turn,long l,long r,long cur)
{
long mid=(l+r)>>1,Len;
LL tmp;
if(l==r)
{
sum[cur]=turn;
if(sum[cur]>=p)sum[cur]%=p;
return;
}
if(goal>mid)
update(goal,turn,mid+1,r,cur<<1|1);
else
update(goal,turn,l,mid,cur<<1);
Len=r-mid;
tmp=pow(b,Len);
sum[cur]=sum[cur<<1]*tmp+sum[cur<<1|1];
if(sum[cur]>=p)sum[cur]%=p;
}
LL query(long L,long R,long l,long r,long cur)
{
long mid=(l+r)>>1,Len;
LL res1,res2,res,tmp;
if(L==l && r==R)return sum[cur];
if(R<=mid)
res=query(L,R,l,mid,cur<<1);
else if(mid<L)
res=query(L,R,mid+1,r,cur<<1|1);
else
{
res1=query(L,mid,l,mid,cur<<1);
res2=query(mid+1,R,mid+1,r,cur<<1|1);
Len=R-mid;
tmp=pow(b,Len);
res=res1*tmp+res2;
if(res>=p)res%=p;
}
return res;
}
int main()
{
long A,B;
char op[10];
freopen("test.txt","r",stdin);
while(scanf("%ld%ld%ld%ld",&b,&p,&len,&n))
{
if(b+p+len+n==0)return 0;
memset(sum,0,sizeof(sum));
while(n--)
{
scanf("%s%ld%ld",op,&A,&B);
if(op[0]=='E')
update(A,B,1,len,1);
if(op[0]=='H')
printf("%lld\n",query(A,B,1,len,1));
}
printf("-\n");
}
return 0;
}



成段更新:

  对将要更新的区间的标号添加一个标记,当递归访问前才根据标记更新到儿子节点.

void PushDown(int cur)
{
       if(lazy[cur])
       {
            flag[cur<<1]=1;
            flag[cur<<1|1]=1;
            lazy[cur]=0;
       }
}

  特别注意PushDown要放在递归边界后,否则可能清除节点标记造成无限递归.

  模板题: hdu 1698,poj 3468

  离散化:

  poj2528 Mayor's posters.

  这题的离散化有个坑, 差>1的相邻两数离散化后中间的空隙就不能被表示出来.

  poj3225 Help whith Intervals

  需要把实数区间离散化,用整数表示,同样有个坑:

  为了表示开闭区间要把原端点拆成2部分,n<<1表示端点n,(n<<1|1)表示[n+ξ ,n+1)

  (ξ表示无穷小)

  第2个坑:

  只用一个数组col[cur]表示这个区间的染色情况:1 染色,0 未染色,-1 表示2种都有.

  这样当col[cur]=-1,而又是面对C或S操作时,就要继续向下update,于是col的成段更新退化成了O(n)

  导致我TLE 5次  T_T

  第3个坑:

  为防止区间更新退化成O(n),再用一个数组xor[cur],表示这个区间是否要被覆盖,每次更新时

  如果col[cur]=-1,把xor[cur]^1就可以了.但是为了防止重复覆盖,PushDown后xor[cur]要清空

  .....情况很多,详见代码.

View Code
#include<stdio.h>
#include<string.h>
#define maxn 65535
int cov[(maxn+1)<<3];
int xo[(maxn+1)<<3];
int flag;
typedef struct node
{
    int l;
    int r;
}node;node now;
void print_range(int l,int r)
{
    if (l&1)printf("(%d,",l>>1);
    else    printf("[%d,",l>>1);

    if (r&1)printf("%d)",(r+1)>>1);
    else    printf("%d]",r>>1);
}
void FXOR(int cur)
{
    if(cov[cur]!=-1)cov[cur]^=1;
    else    xo[cur]^=1;
}
void PushDown(int cur)
{
    if(cov[cur]!=-1)
    {
        cov[cur<<1]=cov[cur];
        cov[cur<<1|1]=cov[cur];
        cov[cur]=-1;
               
/*这个操作很容易忽略,当一个区间得到标记以后,之前的xor[],都没有意义了,
所以要清除*/
        xo[cur<<1]=0,xo[cur<<1|1]=0;
    }
    if(xo[cur])
    {
        FXOR(cur<<1);
        FXOR(cur<<1|1);
        xo[cur]=0;
    }
}
void update(char op,int L,int R,int l,int r,int cur)
{
    int m=(l+r)>>1;
    if(L<=l && r<=R)
    {
        if(op=='U')cov[cur]=1,xo[cur]=0;
        else if(op=='D')cov[cur]=0,xo[cur]=0;
        else if(op=='C' || op=='S')FXOR(cur);
        return;
    }
    PushDown(cur);
    if(L<=m)update(op,L,R,l,m,cur<<1);
    else if(op=='I' || op=='C')cov[cur<<1]=0,xo[cur<<1]=0;

    if(R>m)update(op,L,R,m+1,r,cur<<1|1);
    else if(op=='I' || op=='C')cov[cur<<1|1]=0,xo[cur<<1|1]=0;
}
void query(int l,int r,int cur)
{
    int m=(l+r)>>1;
    if(cov[cur]!=-1)
    {
        if(cov[cur]==0)return;
        if(now.l==-1)
            now.l=l,now.r=r;
        else if(now.r+1>=l)now.r=r;
        else
        {
            if(flag)printf(" ");
            print_range(now.l,now.r);
            flag=1;
            now.l=l,now.r=r;
        }
        return;
    }
/*这个PushDown也很容易被忽略,主要是在有xor[]标记的时候要把它更新下去*/
    PushDown(cur);
    query(l,m,cur<<1);
    query(m+1,r,cur<<1|1);
}
int main()
{
    char op,l,r;
    int a,b;
    cov[1]=0,xo[1]=0;
    while(scanf("%c %c%d,%d%c\n",&op,&l,&a,&b,&r)!=EOF)
    {
        a<<=1;b<<=1;
        if(l=='(')a++;
        if(r==')')b--;
        if( a > b )
        {
            if(op=='I' || op=='C')
                cov[1]=0,xo[1]=0;
        }
        else    update(op,a,b,0,(maxn<<1),1);
    }
    now.l=-1;now.r=-1;
    query(0,maxn<<1,1);
    if(now.l==-1)printf("empty set\n");
    else
    {
        if (flag)printf(" ");
        print_range(now.l,now.r);
        printf("\n");
    }
    return 0;
}

 

  hdu 4288(2012 成都网赛A)

  题目给出n个操作,包括:向序列中插入x并保证序列单调,删除序列中的x,求序列中位置%5=3的所有元素的和.

  网赛时没有想出来,其实就是一个单点更新问题,线段树的每个节点:

  sum[rt][i](0<=i<5)  表示该节点管辖的区间[l,r]中所有位置%5为i的元素的和.

  cnts[rt] 该节点管辖的区间[l,r]中存在多少个元素.

  pushup:

  sum[rt][i]=sum[ls][i] +  sum[rs][  (i -cnts[ls]%5+5)%5].

  为了离散化,需要离线读完全部操作.第一次用stl的unique 和 lower_bound来离散化,感觉效果还可以...

  

coder
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
#include<map>
#define max(a,b) (a)>(b)?(a):(b)
#define min(a,b) (a)<(b)?(a):(b)
#define maxn 100010
using namespace std;
vector<int> V;
typedef long long llong;
struct IN
{
    char s[4];
    int x;
};IN op[maxn];
int cnts[maxn<<2],n,cntv;
llong sum[maxn<<2][5];

void input()
{
    int i;
    V.clear();
    for(i=0;i<n;i++){
        scanf("%s",op[i].s);
        if(op[i].s[0]=='a' || op[i].s[0]=='d'){
            scanf("%d",&op[i].x);
            V.push_back(op[i].x);
        }
    }
    sort(V.begin(),V.end());
    cntv=unique(V.begin(),V.end()) - V.begin();
    V.erase(V.begin()+cntv,V.end());
}
void build_tree(int l,int r,int rt)
{
    int m=(l+r)>>1;
    cnts[rt]=0;
    for(int i=0;i<5;i++)sum[rt][i]=0;
    if(l==r)return;
    build_tree(l,m,rt<<1);
    build_tree(m+1,r,rt<<1|1);
}
void pushup(int rt)
{
    int ls=rt<<1,rs=rt<<1|1,i,t;
    cnts[rt]=cnts[ls]+cnts[rs];
    for(i=0;i<5;i++){
        t=(i-cnts[ls]%5+5)%5;
        sum[rt][i]=sum[ls][i]+sum[rs][t];
    }
}
void ins(int flg,int pos,int l,int r,int rt)
{
    int m=(l+r)>>1;
    if(l==r)
    {
        cnts[rt]+=flg;
        sum[rt][1]+=flg*V[pos];
        return;
    }
    if(pos<=m)ins(flg,pos,l,m,rt<<1);
    else    ins(flg,pos,m+1,r,rt<<1|1);
    pushup(rt);
}
int main()
{
    int t,pos;
    //freopen("test","r",stdin);
    while (scanf("%d",&n)!=EOF)
    {
        input();
        build_tree(0,cntv-1,1);
        for(int i=0;i<n;i++){
            if(op[i].s[0]=='a'){
                t=op[i].x;
                pos=lower_bound(V.begin(),V.begin()+cntv,t)-V.begin();
                ins(1,pos,0,cntv-1,1);
            }else if(op[i].s[0]=='d'){
                t=op[i].x;
                pos=lower_bound(V.begin(),V.begin()+cntv,t)-V.begin();
                ins(-1,pos,0,cntv-1,1);
            }else if(op[i].s[0]=='s')
                printf("%I64d\n",sum[1][3]);
        }
    }
    return 0;
}

 

-----------------------------------更新中-----------------------------------------------------------

 

转载于:https://www.cnblogs.com/eggeek/archive/2012/04/02/2430500.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值