【例题】【线段树】

22 篇文章 0 订阅
16 篇文章 0 订阅

1、
!!!!!注意讨论不能建树的情况
NKOJ 1321 数列操作问题
时间限制 : 10000 MS 空间限制 : 165536 KB

问题描述
假设有一列数{Ai}(1≤i≤n),支持如下两种操作:
将Ak的值加D。(k, D是输入的数)
输出As+As+1+…+At。(s, t都是输入的数,S≤T)

输入格式
第一行一个整数n,
第二行为n个整数,表示{Ai}的初始值≤10000。
第三行为一个整数m,表示操作数
下接m行,每行描述一个操作,有如下两种情况:
ADD k d (表示将Ak加d,1<=k<=n,d为数,d的绝对值不超过10000)
SUM s t (表示输出As+…+At)

输出格式
对于每一个SUM提问,输出结果

样例输入
5
1 2 3 2 4
5
SUM 1 2
SUM 1 5
ADD 1 2
SUM 1 2
SUM 1 5

样例输出
3
12
5
14

提示
M,N<=100000

/*
1、线段树是通过二分思想建立的一颗表示区间关系的树形结构.
2、节点数最多2n-1个
*/

#include<cstdio>
#include<iostream>
using namespace std;
const int need=100003;

struct fy{int a,b,le,ri,val;}tree[need*2];//记录左右儿子就开2×n
//也可以{int a,b,val;}tree[need*4]i*2表示左儿子,i*2+1表示右儿子;不记左右儿子就开4×n

int a[need];
int tot=0,k,d;

void build(int x,int y)
{
    int s=++tot;
    tree[s].a=x,tree[s].b=y;
    //tree[s].val=suma[y]-suma[x-1];
    if(x<y)
    {
        tree[s].le=tot+1;
        build(x,(x+y)/2);
        tree[s].ri=tot+1;
        build((x+y)/2+1,y);
        tree[s].val=tree[tree[s].le].val+tree[tree[s].ri].val;
    }
    else if(x==y) tree[s].val=a[x];
}

void add_(int s)
{
    tree[s].val+=d;
    if(tree[s].le!=0&&tree[tree[s].le].a<=k&&tree[tree[s].le].b>=k) add_(tree[s].le);
    else if(tree[s].ri!=0&&tree[tree[s].ri].a<=k&&tree[tree[s].ri].b>=k) add_(tree[s].ri);
}

int sum_(int s)
{
    if(k<=tree[s].a&&tree[s].b<=d) return tree[s].val;
    int ans=0;
    if(!(tree[tree[s].le].a>d||tree[tree[s].le].b<k)) ans+=sum_(tree[s].le);
    if(!(tree[tree[s].ri].a>d||tree[tree[s].ri].b<k)) ans+=sum_(tree[s].ri);
    /*或:
    if(k<=tree[tree[s].le].b&&tree[tree[s].le].a<=d) ans+=sum_(tree[s].le);
    if(k<=tree[tree[s].ri].b&&tree[tree[s].ri].a<=d) ans+=sum_(tree[s].ri);
    也可以判断是否在区间内
    */
    return ans;
}
/*再或

int sum_(int s)
{
    if(d<tree[s].a||k>tree[s].b) return 0;
    if(k<=tree[s].a&&tree[s].b<=d) return tree[s].val;
    return sum_(s<<1)+sum_((s<<1)|1);
}

*/

int main()
{
    int n;scanf("%d",&n);
    //int a;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        //suma[i]=suma[i-1]+a; 
    } 
    build(1,n);
    int m;scanf("%d",&m);
    string b;
    for(int i=1;i<=m;i++)
    {
        cin>>b;scanf("%d%d",&k,&d);
        if(b=="SUM")  printf("%d\n",sum_(1));
        else if(b=="ADD") add_(1);
    }
} 

2、
NKOJ 1344 人力资源管理
时间限制 : 10000 MS 空间限制 : 65536 KB
问题描述
某公司有n个员工,每个员工有一个工作能力值(该值为60000以内的自然数)。
Tom是公司人力资源部门的主管,他可以进行如下3种操作:
1.Tom为公司招聘了一个能力值为x的新员工
2.Tom为公司辞退了一个能力值为y的员工
3.Tom要查出在所有员工能力值由高到低的排名中,能力值大于W的员工的人数

输入格式
第一行两个整数n,m 表示有n个员工,和tom的m项工作
接下来一行,n个整数,表示n个员工的能力值
接下来m行,每行有两个整数a,b(1<=b<=60000)。
a==1 表示招聘了一个能力值为b的新员工
a==2 表示辞退了一个能力值为b的员工(若没有能力值为b的员工,则该操作无效)
a==3 表示查出能力值>b的员工的个数,并输出结果

输出格式
若干行,每行一个整数,表示输入中所有a==3的操作的结果。

样例输入
样例输入1:
6 5
9 4 6 2 3 5
1 7
1 10
3 6
2 9
3 6
样例输入2:
4 4
4 1 2 5
1 3
3 3
2 4
3 1
样例输出
样例输出1:
3
2
样例输出2:
2
3
提示
n,m<=100000

思路:
1、每个员工的能力值在60000以内,所以建立一个表示区间[1,60001]的线段树
2、对于第三类查询,即为求b+1到max区间中的人数,注意当b+1大于max时直接输出0;

#include<iostream>
#include<cstdio>
using namespace std;
const int need1=60003;
const int need2=100003;

struct fy{int a,b,le,ri,val;}t[need2*2];
struct yf{int mold,num;}c[need2];

int a[need2];
int tot=0,k,ne=-need1;

void build(int x,int y)
{
    int s=++tot;
    t[s].a=x,t[s].b=y;
    if(x==y)
    {
        t[s].val=a[x];
        return ;
    }
    t[s].le=tot+1;build(x,(x+y)/2);
    t[s].ri=tot+1;build((x+y)/2+1,y);
    t[s].val=t[t[s].le].val+t[t[s].ri].val;
}
void add_(int s)
{
     if(t[s].a<=k&&k<=t[s].b) t[s].val++;
     if(t[s].le!=0&&t[t[s].le].a<=k&&k<=t[t[s].le].b) add_(t[s].le);
     else if(t[s].ri!=0&&t[t[s].ri].a<=k&&k<=t[t[s].ri].b) add_(t[s].ri);
} 
void fire_(int s)
{
    if(t[s].a<=k&&k<=t[s].b) t[s].val--;
    if(t[s].le!=0&&t[t[s].le].a<=k&&k<=t[t[s].le].b) fire_(t[s].le);
    else if(t[s].ri!=0&&t[t[s].ri].a<=k&&k<=t[t[s].ri].b) fire_(t[s].ri);
}
int cnt(int s)
{
    if(k+1<=t[s].a&&t[s].b<=ne) return t[s].val;
    int ans=0,mid=(t[s].a+t[s].b)/2;
    if(k<mid) ans+=cnt(t[s].le);
    ans+=cnt(t[s].ri);
    return ans; 
}
int main()
{
    int n,m;scanf("%d%d",&n,&m);
    for(int b,i=1;i<=n;i++)
    {
        scanf("%d",&b);
        if(ne<b) ne=b;
        a[b]++;
    }
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&c[i].mold,&c[i].num);
        if(c[i].num>ne&&c[i].mold==1) ne=c[i].num;
    } 
    build(1,ne);
    for(int i=1;i<=m;i++)
    {
        k=c[i].num;
        if(c[i].mold==2&&a[k])
        {
            a[k]--;
            fire_(1);
        } 
        else if(c[i].mold==1) 
        {
            a[k]++;
            add_(1);
        }
        else if(c[i].mold==3) printf("%d\n",k>=ne?0:cnt(1));
    }
} 
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值