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));
}
}