【SinGuLaRiTy-1023】 Copyright (c) SinGuLaRiTy 2017. All Rights Reserved.
普通平衡树
题目描述
你需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:
1. 插入一个整数x
2. 删除一个整数x(若有多个相同的数,只删除一个)
3. 查询整数x的排名(若有多个相同的数,输出最小的排名),相同的数依次排名,不并列排名
4. 查询排名为x的数,排名的概念同3
5. 求x的前驱(前驱定义为小于x,且最大的数),保证x有前驱
6. 求x的后继(后继定义为大于x,且最小的数),保证x有后继
输入
第一行为n,表示操作的个数(n<=500000)
下面n行每行有两个数opt和x,opt表示操作的序号(1<=opt<=6,-10^7<=x<=10^7)
大规模输入数据,建立读入优化
输出
对于操作3,4,5,6每行输出一个数,表示对应答案
样例数据
样例输入 | 样例输出 |
8 |
2 |
解析
各种平衡树模板。
Code
#include<cstdio>
using namespace std;
struct node
{
int lc,rc;
int sz,v;
}tree[];
int n,op,x,pred,succ,cnt,rt;
char c;
void Read(int &n)
{
n=;
int f=;
while(c>''||c<'')
{
c=getchar();
if(c=='-')
f=-;
}
while(c<=''&&c>='')
{
n=n*+c-'';
c=getchar();
}
n*=f;
}
int buf[];
void Write(int x)
{
if(x<)
{
putchar('-');
x=-x;
}
buf[]=;
while(x)
{
buf[++buf[]]=x%;
x/=;
}
if(!buf[])
{
buf[]=;
buf[]=;
}
while(buf[])
putchar(''+buf[buf[]--]);
putchar();
}
void zig(int &r)
{
int t=tree[r].lc;
tree[r].lc=tree[t].rc;
tree[t].rc=r;
tree[r].sz=tree[tree[r].lc].sz+tree[tree[r].rc].sz+;
tree[t].sz=tree[tree[t].lc].sz+tree[tree[t].rc].sz+;
r=t;
}
void zag(int &r)
{
int t=tree[r].rc;
tree[r].rc=tree[t].lc;
tree[t].lc=r;
tree[r].sz=tree[tree[r].lc].sz+tree[tree[r].rc].sz+;
tree[t].sz=tree[tree[t].lc].sz+tree[tree[t].rc].sz+;
r=t;
}
void zigzag(int &r)
{
zig(tree[r].rc);
zag(r);
}
void zagzig(int &r)
{
zag(tree[r].lc);
zig(r);
}
void maintain(int &r,bool flag)
{
if(!flag)
{
if(tree[tree[r].rc].sz<tree[tree[tree[r].lc].lc].sz)
zig(r);
else if(tree[tree[r].rc].sz<tree[tree[tree[r].lc].rc].sz)
zagzig(r);
else
return;
}
else
{
if(tree[tree[r].lc].sz<tree[tree[tree[r].rc].rc].sz)
zag(r);
else if(tree[tree[r].lc].sz<tree[tree[tree[r].rc].lc].sz)
zigzag(r);
else
return;
}
maintain(tree[r].lc,);
maintain(tree[r].rc,);
maintain(r,);
maintain(r,);
}
void Insert(int &r,int x)
{
if(!r)
{
tree[++cnt].sz=;
tree[cnt].v=x;
r=cnt;
return;
}
tree[r].sz++;
x<tree[r].v ? Insert(tree[r].lc,x) : Insert(tree[r].rc,x);
maintain(r,x>=tree[r].v);
}
int del(int &r,int x)
{
int res;
tree[r].sz-=;
if(tree[r].v==x||(==tree[r].lc&&x<tree[r].v)||(==tree[r].rc&&x>tree[r].v))
{
res=tree[r].v;
if(==tree[r].lc||==tree[r].rc)
r=tree[r].lc+tree[r].rc;
else
tree[r].v=del(tree[r].lc,x);
}
else
res=(x<tree[r].v ? del(tree[r].lc,x) : del(tree[r].rc,x));
return res;
}
void predecessor(int r,int x)
{
if(!r)
return;
if(x>tree[r].v)
{
pred=tree[r].v;
predecessor(tree[r].rc,x);
}
else
predecessor(tree[r].lc,x);
}
void successor(int r,int x)
{
if(!r)
return;
if(x<tree[r].v)
{
succ=tree[r].v;
successor(tree[r].lc,x);
}
else
successor(tree[r].rc,x);
}
int kth(int r,int x)
{
if(x==tree[tree[r].lc].sz+)
return tree[r].v;
return (x<tree[tree[r].lc].sz+ ? kth(tree[r].lc,x) : kth(tree[r].rc,x-tree[tree[r].lc].sz-));
}
int rnk(int r,int x)
{
if(!r)
return ;
return (x<=tree[r].v ? rnk(tree[r].lc,x) : rnk(tree[r].rc,x)+tree[tree[r].lc].sz+);
}
int main()
{
Read(n);
for(register int i=;i<=n;i++)
{
Read(op);Read(x);
switch(op)
{
case : Insert(rt,x); break;
case : del(rt,x); break;
case : Write(rnk(rt,x)); break;
case : Write(kth(rt,x)); break;
case : predecessor(rt,x); Write(pred); break;
default: successor(rt,x); Write(succ); break;
}
}
return ;
}
宠物收养所
题目描述
最近,阿Q开了一间宠物收养所。收养所提供两种服务:收养被主人遗弃的宠物和让新的主人领养这些宠物。每个领养者都希望领养到自己满意的宠物,阿Q根据领养者的要求通过他自己发明的一个特殊的公式,得出该领养者希望领养的宠物的特点值a(a是一个正整数,a<2^31),而他也给每个处在收养所的宠物一个特点值。这样他就能够很方便的处理整个领养宠物的过程了,宠物收养所总是会有两种情况发生:被遗弃的宠物过多或者是想要收养宠物的人太多,而宠物太少。
1. 被遗弃的宠物过多时,假若到来一个领养者,这个领养者希望领养的宠物的特点值为a,那么它将会领养一只目前未被领养的宠物中特点值最接近a的一只宠物。(任何两只宠物的特点值都不可能是相同的,任何两个领养者的希望领养宠物的特点值也不可能是一样的)如果有两只满足要求的宠物,即存在两只宠物他们的特点值分别为a-b和a+b,那么领养者将会领养特点值为a-b的那只宠物。
2. 收养宠物的人过多,假若到来一只被收养的宠物,那么哪个领养者能够领养它呢?能够领养它的领养者,是那个希望被领养宠物的特点值最接近该宠物特点值的领养者,如果该宠物的特点值为a,存在两个领养者他们希望领养宠物的特点值分别为a-b和a+b,那么特点值为a-b的那个领养者将成功领养该宠物。 一个领养者领养了一个特点值为a的宠物,而它本身希望领养的宠物的特点值为b,那么这个领养者的不满意程度为abs(a-b)。
【任务描述】
你得到了一年当中,领养者和被收养宠物到来收养所的情况,希望你计算所有收养了宠物的领养者的不满意程度的总和。这一年初始时,收养所里面既没有宠物,也没有领养者。
输入
第一行为一个正整数n,n<=80000,表示一年当中来到收养所的宠物和领养者的总数。接下来的n行,按到来时间的先后顺序描述了一年当中来到收养所的宠物和领养者的情况。每行有两个正整数a, b,其中a=0表示宠物,a=1表示领养者,b表示宠物的特点值或是领养者希望领养宠物的特点值。(同一时间呆在收养所中的,要么全是宠物,要么全是领养者,这些宠物和领养者的个数不会超过10000个)
输出
仅有一个正整数,表示一年当中所有收养了宠物的领养者的不满意程度的总和mod 1000000以后的结果。
样例数据
样例输入 | 样例输出 |
5 |
3 |
解析
利用set中元素的单调性进行二分查找匹配,插入复杂度logn,匹配复杂度logn 。
(对于set这种简化代码的终极武器,有必要好好研究一下)
Code
#include<cstring>
#include<cmath>
#include<algorithm>
#include<cstdlib>
#include<cstdio>
#include<set>
#include<iostream> #define ll long long const int mod=;
const int INF=; using namespace std; int n;
int ans;
bool flag;
set<int> T; int main()
{
scanf("%d",&n);
T.insert(-INF);
T.insert(INF);
while(n--)
{
int tag,data;
scanf("%d%d",&tag,&data);
if(T.size()==)
{
flag=tag;
T.insert(data);
}
else if(tag!=flag)
{
int small=*--T.lower_bound(data);
int big=*T.lower_bound(data);
if(data-small<=big-data&&small>-INF)
{
ans=(ans+data-small)%mod;
T.erase(small);
}
else
{
ans=(ans+big-data)%mod;
T.erase(big);
}
}
else
T.insert(data);
}
printf("%d",ans);
return ;
}
郁闷的出纳员
题目描述
OIER公司是一家大型专业化软件公司,有着数以万计的员工。作为一名出纳员,我的任务之一便是统计每位员工的工资。
这本来是一份不错的工作,但是令人郁闷的是,我们的老板反复无常,经常调整员工的工资。
如果他心情好,就可能把每位员工的工资加上一个相同的量。
反之,如果心情不好,就可能把他们的工资扣除一个相同的量。
我真不知道除了调工资他还做什么其它事情。
工资的频繁调整很让员工反感,尤其是集体扣除工资的时候,一旦某位员工发现自己的工资已经低于了合同规定的工资下界,他就会立刻气愤地离开公司,并且再也不会回来了。
每位员工的工资下界都是统一规定的。
每当一个人离开公司,我就要从电脑中把他的工资档案删去,同样,每当公司招聘了一位新员工,我就得为他新建一个工资档案。
老板经常到我这边来询问工资情况,他并不问具体某位员工的工资情况,而是问现在工资第k多的员工拿多少工资。每当这时,我就不得不对数万个员工进行一次漫长的排序,然后告诉他答案。
好了,现在你已经对我的工作了解不少了。正如你猜的那样,我想请你编一个工资统计程序。怎么样,不是很困难吧?
输入
第一行有两个非负整数n和min。n表示下面有多少条命令,min表示工资下界。
接下来的n行,每行表示一条命令。命令可以是以下四种之一:
名称 格式 作用
I命令 I_k 新建一个工资档案,初始工资为k。如果某员工的初始工资低于工资下界,他将立刻离开公司。
A命令 A_k 把每位员工的工资加上k
S命令 S_k 把每位员工的工资扣除k
F命令 F_k 查询第k多的工资
_(下划线)表示一个空格,I命令、A命令、S命令中的k是一个非负整数,F命令中的k是一个正整数。
在初始时,可以认为公司里一个员工也没有。
<数据范围>
I命令的条数不超过100000
A命令和S命令的总条数不超过100
F命令的条数不超过100000
每次工资调整的调整量不超过1000
新员工的工资不超过100000
输出
输出文件的行数为F命令的条数加一。
对于每条F命令,你的程序要输出一行,仅包含一个整数,为当前工资第k多的员工所拿的工资数,如果k大于目前员工的数目,则输出-1。
输出文件的最后一行包含一个整数,为离开公司的员工的总数。
样例数据
样例输入 | 样例输出 |
9 10 |
10 |
解析
用splay做。
操作中较为难办的就是相同数和删除区间。
相同数解决方法:保证每个数都不同,用num记录其个数
删除区间[l, r]:实际上是将l的前驱p,r的后继q,将p移动到q的子节点,直接删除p的右子树,然后更新即可。相比对序列操作的l 和 r可能不存在,需要特殊处理一下。
再者就是各种细节:
1. 初始工资小于MIN的不算踢出的......
2. 各种PUSHUP,PUSHDOWN注意。
Code
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 100010 #define ls(x) tr[x].l
#define rs(x) tr[x].r
#define fa(x) tr[x].fa using namespace std; struct node
{
int l,r,fa,size,v;
};
node tr[N]; int n,m,tot,root,delta,sum; void PushUp(int x)
{
tr[x].size=tr[ls(x)].size+tr[rs(x)].size+;
} void zig(int x)
{
int y=fa(x);
int z=fa(y);
if(y==ls(z))
ls(z)=x;
else
rs(z)=x;
fa(x)=z,ls(y)=rs(x),fa(y)=x,fa(rs(x))=y,rs(x)=y;
PushUp(y),PushUp(x);
if(y==root)
root=x;
} void zag(int x)
{
int y=fa(x);
int z=fa(y);
if(y==ls(z))
ls(z)=x;
else
rs(z)=x;
fa(x)=z,rs(y)=ls(x),fa(y)=x,fa(ls(x))=y,ls(x)=y;
PushUp(y),PushUp(x);
if(y==root)
root=x;
} void Splay(int x,int d)
{
while(fa(x)!=d)
{
if(ls(fa(x))==x)
zig(x);
else
zag(x);
}
} void Insert(int x)
{
if(!root)
{
root=++tot;
tr[tot].v=x,tr[tot].size=;
return ;
}
int p=root,z;
while(p)
{
z=p;
tr[p].size++;
if(x<tr[p].v)p=ls(p);
else p=rs(p);
}
if(x<tr[z].v)
ls(z)=++tot;
else
rs(z)=++tot;
tr[tot].v=x,tr[tot].size=,fa(tot)=z;
Splay(tot,);
} int del(int &x,int f)
{
if(!x)
return ;
int k;
if(tr[x].v+delta<m)
{
k=del(rs(x),x)+tr[ls(x)].size+;
tr[rs(x)].size=tr[x].size-k;
x=rs(x),fa(x)=f;
}
else
{
k=del(ls(x),x);
tr[x].size-=k;
}
return k;
} int query(int x,int k)
{
if(k<=tr[rs(x)].size)
return query(rs(x),k);
if(k==tr[rs(x)].size+)
return tr[x].v;
return query(ls(x),k-tr[rs(x)].size-);
} int main()
{
cin>>n>>m;
for(int i=;i<=n;i++)
{
char s[];
int x;
scanf("%s%d",s,&x);
if(s[]=='I'&&x>=m)
Insert(x-delta);
else if(s[]=='A')
delta+=x;
else if(s[]=='S')
delta-=x,sum+=del(root,);
else if(s[]=='F'&&x>tr[root].size)
puts("-1");
else if(s[]=='F')
printf("%d\n",query(root,x)+delta);
}
cout<<sum<<endl;
return ;
}
二逼平衡树
题目描述
输入
输出
对于操作1,2,4,5各输出一行,表示查询结果
样例数据
样例输入 | 样例输出 |
9 6 |
2 |
提示
1.n和m的数据范围:n,m<=50000
2.序列中每个数的数据范围:[0,1e8]
3.虽然原题没有,但事实上5操作的k可能为负数
解析
树套树标准模板,没有什么特别的。
不过值得注意的是,洛谷上的用户kczno1中指出“树套树的做法是暴力”(我也这么觉得,可能是我写丑了,太慢了......),而应该用二分答案跑CDQ [简易CDQ分治讲解]。下面是其思路:
对于查询k大,我们得到左边的个数num,如果k<=num,去左边,否则k-=num,去右边,否则k<=mid分左,否则分右边。
对于查询rank,我们只在k>mid的时候,用num更新答案。
对于查询前驱,我们只在k>mid的时候,用左边的对应区间max更新答案(线段树维护即可)。
而对于查询后继,我们通过将所有数取反来将后继转化为前驱处理。
此外,还有一种或许更优秀的解法:树状数组套动态加点线段树。
Code
//由于是树形数据结构复习,当然还是写的树套树
#include<iostream>
#include<cstdio>
#include<cstdlib> #define N 50005
#define M 3000005
#define inf 1000000007 using namespace std; int n,m,cnt,tmp;
int a[N],l[N<<],r[N<<],root[N<<];
int ls[M],rs[M],rnd[M],Size[M],sum[M],v[M]; inline int read()
{
int a=,f=; char c=getchar();
while (c<''||c>'') {if (c=='-') f=-; c=getchar();}
while (c>=''&&c<='') {a=a*+c-''; c=getchar();}
return a*f;
} inline void pushup(int k)
{
Size[k]=Size[ls[k]]+Size[rs[k]]+sum[k];
} inline void lturn(int &k)
{
int t=rs[k];
rs[k]=ls[t];
ls[t]=k;
pushup(k);
pushup(t);
k=t;
} inline void rturn(int &k)
{
int t=ls[k];
ls[k]=rs[t];
rs[t]=k;
pushup(k);
pushup(t);
k=t;
} void Insert(int &k,int num)
{
if(!k)
{
k=++cnt;
Size[k]=;
sum[k]=;
v[k]=num;
rnd[k]=rand();
return;
}
Size[k]++;
if(num==v[k])
sum[k]++;
else if(num<v[k])
{
Insert(ls[k],num);
if(rnd[ls[k]]<rnd[k])
rturn(k);
}
else
{
Insert(rs[k],num);
if(rnd[rs[k]]<rnd[k])
lturn(k);
}
}
void pre(int k,int x,int y)
{
l[k]=x;
r[k]=y;
if(l[k]==r[k])
return;
int mid=l[k]+r[k]>>;
pre(k<<,x,mid); pre(k<<|,mid+,y);
}
void build(int k,int id,int num)
{
Insert(root[k],num);
if(l[k]==r[k])
return;
int mid=l[k]+r[k]>>;
if(id<=mid)
build(k<<,id,num);
else
build(k<<|,id,num);
}
inline void ask_rank(int k,int num)
{
if(!k) return;
if(num==v[k])
{
tmp+=Size[ls[k]];
return;
}
else if(num<v[k])
ask_rank(ls[k],num);
else
{
tmp+=Size[ls[k]]+sum[k];
ask_rank(rs[k],num);
}
}
inline void get_rank(int k,int x,int y,int num)
{
if (l[k]==x&&r[k]==y)
{
ask_rank(root[k],num);
return;
}
int mid=l[k]+r[k]>>;
if(y<=mid)
get_rank(k<<,x,y,num);
else if(x>mid)
get_rank(k<<|,x,y,num);
else
get_rank(k<<,x,mid,num),get_rank(k<<|,mid+,y,num);
}
inline void get_index(int x,int y,int z)
{
int l=,r=inf,ans;
while (l<=r)
{
int mid=l+r>>;
tmp=;
get_rank(,x,y,mid);
if(tmp<=z)
ans=mid,l=mid+;
else
r=mid-;
}
printf("%d\n",ans);
}
void del(int &k,int num)
{
if (v[k]==num)
{
if(sum[k]>)
{
sum[k]--;
Size[k]--;
return;
}
if(ls[k]*rs[k]==)
k=ls[k]+rs[k];
else if(rnd[ls[k]]<rnd[rs[k]])
rturn(k),del(k,num);
else
lturn(k),del(k,num);
}
else if(num<v[k])
del(ls[k],num),Size[k]--;
else
del(rs[k],num),Size[k]--;
}
inline void change(int k,int x,int num,int y)
{
del(root[k],y);
Insert(root[k],num);
if(l[k]==r[k])
return;
int mid=l[k]+r[k]>>;
if(x<=mid)
change(k<<,x,num,y);
else
change(k<<|,x,num,y);
}
void before(int k,int num)
{
if(!k)
return;
if(v[k]<num)
tmp=max(v[k],tmp),before(rs[k],num);
else
before(ls[k],num);
}
void after(int k,int num)
{
if(!k)
return;
if(v[k]>num)
tmp=min(v[k],tmp),after(ls[k],num);
else
after(rs[k],num);
}
void ask_after(int k,int x,int y,int num)
{
if(l[k]==x&&r[k]==y)
{
after(root[k],num);
return;
}
int mid=l[k]+r[k]>>;
if(y<=mid)
ask_after(k<<,x,y,num);
else if(x>mid)
ask_after(k<<|,x,y,num);
else
ask_after(k<<,x,mid,num),ask_after(k<<|,mid+,y,num);
}
void ask_before(int k,int x,int y,int num)
{
if(l[k]==x&&r[k]==y)
{
before(root[k],num);
return;
}
int mid=l[k]+r[k]>>;
if(y<=mid)
ask_before(k<<,x,y,num);
else if(x>mid)
ask_before(k<<|,x,y,num);
else
ask_before(k<<,x,mid,num),ask_before(k<<|,mid+,y,num);
}
int main()
{
n=read();
m=read();
pre(,,n);
for(int i=;i<=n;i++)
a[i]=read(),build(,i,a[i]);;
for(int i=;i<=m;i++)
{
int opt=read();
int x=read(),y=read(),k;
switch(opt)
{
case :
k=read();
tmp=;
get_rank(,x,y,k);
printf("%d\n",tmp);
break;
case :
k=read();
get_index(x,y,k);
break;
case :
change(,x,y,a[x]);
a[x]=y;
break;
case :
k=read();
tmp=;
ask_before(,x,y,k);
printf("%d\n",tmp);
break;
case :
k=read();
tmp=inf;
ask_after(,x,y,k);
printf("%d\n",tmp);
break;
}
}
return ;
}
郁闷的小J
题目描述
小J是国家图书馆的一位图书管理员,他的工作是管理一个巨大的书架。虽然他很能吃苦耐劳,但是由于这个书架十分巨大,所以他的工作效率总是很低,以致他面临着被解雇的危险,这也正是他所郁闷的。
具体说来,书架由N个书位组成,编号从1到N。每个书位放着一本书,每本书有一个特定的编码。
小J的工作有两类:
1. 图书馆经常购置新书,而书架任意时刻都是满的,所以只得将某位置的书拿掉并换成新购的书。
2. 小J需要回答顾客的查询,顾客会询问某一段连续的书位中某一特定编码的书有多少本。
例如,共5个书位,开始时书位上的书编码为1,2,3,4,5
一位顾客询问书位1到书位3中编码为“2”的书共多少本,得到的回答为:1
一位顾客询问书位1到书位3中编码为“1”的书共多少本,得到的回答为:1
此时,图书馆购进一本编码为“1”的书,并将它放到2号书位。
一位顾客询问书位1到书位3中编码为“2”的书共多少本,得到的回答为:0
一位顾客询问书位1到书位3中编码为“1”的书共多少本,得到的回答为:2
……
你的任务是写一个程序来回答每个顾客的询问。
输入
第一行两个整数N,M,表示一共N个书位,M个操作。
接下来一行共N个整数数A1,A2…AN,Ai表示开始时位置i上的书的编码。
接下来M行,每行表示一次操作,每行开头一个字符
若字符为‘C’,表示图书馆购进新书,后接两个整数A(1<=A<=N),P,表示这本书被放在位置A上,以及这本书的编码为P。
若字符为‘Q’,表示一个顾客的查询,后接三个整数A,B,K(1<=A<=B<=N),表示查询从第A书位到第B书位(包含A和B)中编码为K的书共多少本。
<数据范围>
对于40%的数据,1<=N,M<=5000
对于100%的数据,1<=N,M<=100000
对于100%的数据,所有出现的书的编码为不大于2147483647的正数。
输出
对每一个顾客的查询,输出一个整数,表示顾客所要查询的结果。
样例数据
样例输入 | 样例输出 |
5 5 |
1 |
解析
套平衡树模板,思路简单,代码较难......
Code
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath> #define N 200005
#define inf 0x3f3f3f3f using namespace std; int ls[N],rs[N],h[N],data[N],wz[N],Size[N],cnt,root; int zig(int r)
{
int p=ls[r];
ls[r]=rs[p];
rs[p]=r;
h[r]=max(h[ls[r]],h[rs[r]])+;
Size[r]=Size[ls[r]]+Size[rs[r]]+;
h[p]=max(h[ls[p]],h[rs[p]])+;
Size[p]=Size[ls[p]]+Size[rs[p]]+;
return p;
} int zag(int r)
{
int p=rs[r];
rs[r]=ls[p];
ls[p]=r;
h[r]=max(h[ls[r]],h[rs[r]])+;
Size[r]=Size[ls[r]]+Size[rs[r]]+;
h[p]=max(h[ls[p]],h[rs[p]])+;
Size[p]=Size[ls[p]]+Size[rs[p]]+;
return p;
} int zagzig(int r)
{
ls[r]=zag(ls[r]);
return zig(r);
} int zigzag(int r)
{
rs[r]=zig(rs[r]);
return zag(r);
} void Move(int &r)
{
if(h[ls[r]]-h[rs[r]]==)
{
if(h[ls[ls[r]]]>h[rs[ls[r]]])
r=zig(r);
else
r=zagzig(r);
}
else
if(h[ls[r]]-h[rs[r]]==-)
{
if(h[ls[rs[r]]]<h[rs[rs[r]]])
r=zag(r);
else
r=zigzag(r);
}
h[r]=max(h[ls[r]],h[rs[r]])+;
Size[r]=Size[ls[r]]+Size[rs[r]]+;
} void Insert(int a,int w,int &r)
{
if(!r)
{
r=++cnt;
h[r]=;
data[r]=a;
Size[r]=;
wz[r]=w;
return;
}
if(data[r]<a||(data[r]==a&&wz[r]<w))
Insert(a,w,rs[r]);
else
Insert(a,w,ls[r]);
Move(r);
} void Delete(int a,int w,int &r)
{
if(!r)
return;
if(data[r]==a&&wz[r]==w)
{
if(!ls[r]||!rs[r])
{
r=ls[r]+rs[r];
if(r==)
return;
}
else
{
int p=ls[r];
while(rs[p])
p=rs[p];
data[r]=data[p];
wz[r]=wz[p];
Delete(data[p],wz[p],ls[r]);
}
}
else
{
if(data[r]<a||(data[r]==a&&wz[r]<w))
Delete(a,w,rs[r]);
else
Delete(a,w,ls[r]);
}
Move(r);
} int getint()
{
int p=,f=;
char c=getchar();
while(c<''||c>'')
{
if(c=='-')f=;
c=getchar();
}
while(c>=''&&c<='')
{
p=p*+c-'';
c=getchar();
}
if(f)p=-p;
return p;
} int Find(int a,int up,int r)
{
if(!r)
return ;
if(data[r]==a&&wz[r]==up)
return Size[ls[r]]+;
else
{
if(data[r]<a||(data[r]==a&&wz[r]<up))
return Size[ls[r]]++Find(a,up,rs[r]);
else
return Find(a,up,ls[r]);
}
} int n,m,book[N];
char opt[]; int main()
{
n=getint();m=getint();
for(int i=;i<=n;i++)
{
book[i]=getint();
Insert(book[i],i,root);
}
for(int i=;i<=m;i++)
{
scanf("%s",opt);
if(opt[]=='C')
{
int a=getint();
Delete(book[a],a,root);
book[a]=getint();
Insert(book[a],a,root);
}
if(opt[]=='Q')
{
int lef=getint(),rig=getint(),a=getint();
int p=Find(a,rig,root),q=Find(a,lef-,root);
printf("%d\n",p-q);
}
}
return ;
}
维护序列
题目描述
老师交给小可可一个维护数列的任务,现在小可可希望你来帮他完成。
有长为N的数列,不妨设为a1,a2,…,aN 。有如下三种操作形式:
(1)把数列中的一段数全部乘一个值;
(2)把数列中的一段数全部加一个值;
(3)询问数列中的一段数的和
由于答案可能很大,你只需输出这个数模P的值。
输入
第一行两个整数N和P(1≤P≤1000000000)。
第二行含有N个非负整数,从左到右依次为a1,a2,…,aN, (0≤ai≤1000000000,1≤i≤N)。
第三行有一个整数M,表示操作总数。
从第四行开始每行描述一个操作,输入的操作有以下三种形式:
操作1:“1 t g c”(不含双引号)。表示把所有满足t≤i≤g的ai改为ai×c (1≤t≤g≤N,0≤c≤1000000000)。
操作2:“2 t g c”(不含双引号)。表示把所有满足t≤i≤g的ai改为ai+c (1≤t≤g≤N,0≤c≤1000000000)。
操作3:“3 t g”(不含双引号)。询问所有满足t≤i≤g的ai的和模P的值 (1≤t≤g≤N)。 同一行相邻两数之间用一个空格隔开,每行开头和末尾没有多余空格。
输出
对每个操作3,按照它在输入中出现的顺序,依次输出一行一个整数表示询问结果。
样例数据
样例输入 | 样例输出 |
7 43 |
2 |
解析
很显然,本题要用线段树做,但是怎么用线段树倒是个问题.可以想到先建树,每次修改一个区间的值把这个区间所包含的区间的值给修改了即可,但是这样太花时间了,有没有省时间的方法呢?我们就要使用"懒标记"了。[忘记lazy-tag?]
简单来说,如果要修改一个区间的值,如果这个区间刚好被完全包含了就直接返回,打上标记(要改成多少),那么下次要用子节点的时候将标记下传即可,显然,对于本题要打两个tag,那么sum[o] = sum[o] * mul[o] + add[o],sum为和,mul为乘的数,add为加的数,那么如果我们乘一个数c,sum[o] = sum[o] * mul[o] * c + add[o] * c,将mul[o] * c和add[o] * c看作两个整体,那么可以发现如果乘一个数c的话,add数组要*c,mul数组也要*c,同理,如果加一个数c,那么只需要add数组+c即可,因为在下传标记的时候既有加,又有减(可能为0),所以add和mul数组的计算一定要将这两种情况都计算到.
如果在线段树上几个区间操作的性质相同的,先把要求的数用操作需要的量给表示出来,然后对于每一种操作看看需要维护的标记的变化,最后合并一下即可.
Code
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm> using namespace std; const long long maxn=; long long n,p,a[maxn],add[maxn],mul[maxn],sum[maxn],m; void build(int l,int r,int o)
{
mul[o]=;
add[o]=;
if(l==r)
{
sum[o]=a[l];
return;
}
int mid=(l+r)>>;
build(l,mid,o*);
build(mid+,r,o*+);
sum[o]=(sum[o*]+sum[o*+])%p;
} void pushdown(int o,int k)
{
sum[o*]=(sum[o*]*mul[o]+add[o]*(k-(k>>)))%p;
sum[o*+]=(sum[o*+]*mul[o]+add[o]*(k>>))%p;
mul[o*]=mul[o*]*mul[o]%p;
mul[o*+]=mul[o*+]*mul[o]%p;
add[o*]=(add[o*]*mul[o]+add[o])%p;
add[o*+]=(add[o*+]*mul[o]+add[o])%p;
mul[o]=;
add[o]=;
} void jia(int l,int r,int o,int x,int y,int v)
{
if(x<=l&&r<=y)
{
add[o]=(add[o]+v)%p;
sum[o]=(sum[o]+v*(r-l+))%p;
return;
}
pushdown(o,r-l+);
int mid=(l+r)>>;
if(x<=mid)
jia(l,mid,o*,x,y,v);
if (y>mid)
jia(mid+,r,o*+,x,y,v);
sum[o]=(sum[o*]+sum[o*+])%p;
} void cheng(int l,int r,int o,int x,int y,int v)
{
if(x<=l&&r<=y)
{
add[o]=(add[o]*v)%p;
mul[o]=(mul[o]*v)%p;
sum[o]=(sum[o]*v)%p;
return;
}
pushdown(o,r-l+);
int mid=(l+r)>>;
if(x<=mid)
cheng(l,mid,o*,x,y,v);
if(y>mid)
cheng(mid+,r,o*+,x,y,v);
sum[o]=(sum[o*]+sum[o*+])%p;
} long long query(int l,int r,int o,int x,int y)
{
if(x<=l&&r<=y)
return sum[o]%p;
int mid=(l+r)>>;
pushdown(o,r-l+);
long long temp=;
if (x<=mid)
temp=(temp+query(l,mid,o*,x,y))%p;
if (y > mid)
temp=(temp+query(mid+,r,o*+,x,y))%p;
sum[o]=(sum[o*]+sum[o*+])%p;
return temp%p;
} int main()
{
scanf("%lld%lld",&n,&p);
for(int i=;i<=n;i++)
scanf("%lld",&a[i]);
build(,n,);
scanf("%lld",&m);
while(m--)
{
int op,t,g,c;
scanf("%d%d%d",&op,&t,&g);
if(op==)
{
scanf("%d",&c);
cheng(,n,,t,g,c);
}
if(op==)
{
scanf("%d",&c);
jia(,n,,t,g,c);
}
if(op==)
printf("%lld\n",query(,n,,t,g)%p);
}
return ;
}
排序机械臂
题目描述
为了把工厂中高低不等的物品按从低到高排好序,工程师发明了一种排序机械臂。它遵循一个简单的排序规则,第一次操作找到摄低的物品的位置P1,并把左起第一个至P1间的物品反序;第二次找到第二低的物品的位置P2,并把左起第二个至P2间的物品反序...最终所有的物品都会被排好序。
上图给出一个示例,第一次操作前,菝低的物品在位置4,于是把第1至4的物品反序;第二次操作前,第二低的物品在位罝6,于是把第2至6的物品反序...
你的任务便是编写一个程序,确定一个操作序列,即每次操作前第i低的物品所在位置Pi,以便机械臂工作。需要注意的是,如果有高度相同的物品,必须保证排序后它们的相对位置关系与初始时相同。
输入
第一行包含正整数n,表示需要排序的物品数星。
第二行包含n个空格分隔的整数ai,表示每个物品的高度。
输出
输出一行包含n个空格分隔的整数Pi。
样例数据
样例输入 | 样例输出 |
6 |
4 6 4 5 6 6 |
解析
思路:splay 区间反转+查询
首先我们进行一次排序,使高度小的在前,若高度相同,位置靠前的在前;然后我们再建立平衡树,平衡树中直接存储物品的编号,即加上两个虚拟节点后,平衡树中的节点x,对应初始序列位置为x的点;平衡树的中序遍历,对应本次排序后的物品顺序。(物品的高度只在排序中有用,自建树开始高度完全没有用了);对于输出物品位置p,将物品旋转至根节点,输出左子树大小+1即可;注意在区间反转时维护反转标记。
Code
#include<cstdio>
#include<algorithm> #define N 100005 using namespace std; int n,fa[N],siz[N],ch[N][],root,tag[N]; struct node
{
int i,k;
}a[N]; inline bool cmp(node p,node q)
{
if(p.k<q.k) return ;
if(p.k==q.k) return p.i<q.i;
return ;
} inline void update(int k)
{
siz[k]=siz[ch[k][]]+siz[ch[k][]]+;
} inline void build(int l,int r,int f)
{
if(l>r)
return;
int mid=l+r>>;
if(mid<f)
ch[f][]=mid;
else
ch[f][]=mid;
siz[mid]=;
fa[mid]=f;
if(l==r)
return;
build(l,mid-,mid);
build(mid+,r,mid);
update(mid);
} inline void down(int k)
{
tag[k]^=;
if(ch[k][])
tag[ch[k][]]^=;
if(ch[k][])
tag[ch[k][]]^=;
swap(ch[k][],ch[k][]);
} inline void rotate(int x,int & goal)
{
int y=fa[x],z=fa[y],kind=ch[y][]==x;
if(y==goal)
goal=x;
else
ch[z][ch[z][]==y]=x;
ch[y][kind]=ch[x][kind^];
ch[x][kind^]=y;
fa[y]=x;fa[x]=z;
fa[ch[y][kind]]=y;
update(y);
} inline void splay(int x,int & goal)
{
while(x!=goal)
{
int y=fa[x],z=fa[y];
if(tag[z])
down(z);
if(tag[y])
down(y);
if(tag[x])
down(x);
if(y!=goal)
{
if(ch[y][]==x^ch[z][]==y)
rotate(x,goal);
else
rotate(y,goal);
}
rotate(x,goal);
update(x);
}
} inline int find(int now,int k)
{
if(tag[now])
down(now);
int l=ch[now][],r=ch[now][];
if(siz[l]+==k)
return now;
if(k<=siz[l])
return find(l,k);
return find(r,k-siz[l]-);
} inline void reverse(int l,int r)
{
int x=find(root,l-),y=find(root,r+);
splay(x,root);
splay(y,ch[x][]);
tag[ch[y][]]^=;
} int main()
{
scanf("%d",&n);
for(int i=;i<=n;i++)
{
scanf("%d",&a[i+].k);
a[i+].i=i+;
}
a[].i=;
a[n+].i=n+;
a[n+].k=;
sort(a+,a+n+,cmp);
build(,n+,);
root=n+>>;
int p;
for(int i=;i<=n;i++)
{
splay(a[i].i,root);
p=siz[ch[root][]]+;
printf("%d ",p-);
reverse(i,p);
}
printf("%d",n);
return ;
}
维修数列
题目描述
请写一个程序,要求维护一个数列,支持以下6种操作:(注意:格式栏中的下划线"_"表示实际输入文件中的空格)
输入
输入的第1 行包含两个数N 和M(M ≤20 000),N表示初始时数列中数的个数,M表示要进行的操作数目。
第2行包含N个数字,描述初始时的数列。
以下M行,每行一条命令,格式参见问题描述中的表格。
任何时刻数列中最多含有500,000个数,数列中任何一个数字均在[-1000, 1000]内。
插入的数字总数不超过4,000,000个,输入文件大小不超过20M Bytes。
输出
对于输入数据中的GET-SUM和MAX-SUM操作,向输出文件依次打印结果,每个答案(数字)占一行。
样例数据
样例输入 | 样例输出 |
9 8 |
-1 |
<样例解释>
初始时,我们拥有数列:
执行操作GET-SUM_5_4,表示求出数列中第5个数字开始连续4个数字之和,即如下图所示,应为1+(-5)+(-3)+6=-1
执行操作MAX-SUM,表示求出当前数列中最大的一段和,即如下图所示,应为3+5+1+(-5)+(-3)+6+3=10
执行操作INSERT_8_3_-5_7_2,即在数列的第8个数字后插入数字-5,7,2,如下图的深色部分:
执行操作DELETE_12_1,即删除数列的第12个数字,即最后一个:
执行操作MAKE-SAME_3_3_2,表示数列中从第3个数字开始的连续3个数字,即下图所示的深色部分,全部修改为2:
改为
执行操作REVERSE_3_6,表示取出数列中从第3个数字开始的连续6个数,
如上图所示的深色部分2,2,2,-5,-3,6,翻转后得到6,-3,-5,2,2,2,并放回原来的位置:
最后执行GET-SUM_5_4和MAX-SUM,不难得到答案1和10。
<数据规模及约定>
>你可以认为在任何时刻,数列中至少有1个数。
>输入数据一定是正确的,即指定位置的数在数列中一定存在。
>50%的数据中,任何时刻数列中最多含有30 000个数;100%的数据中,任何时刻>数列中最多含有500 000个数。
>100%的数据中,任何时刻数列中任何一个数字均在[-1000, 1000]内。
>100%的数据中,M ≤20 000,插入的数字总数不超过4 000 000个,输入文件大小不超过20M Bytes
解析
<这道题自己没思路,于是看了看Candy?的博客,茅塞顿开啊~>
splay序列操作"裸题"。
需要回收节点编号,所以用到sz和nw()sz和nw(),通常维护序列是不用sz的;
splay维护的是这个序列,不再存在平衡树左小右大的性质;
操作一段区间[l,r][l,r],将l-1 splay到根,r+1 splay到右儿子,他的左儿子就是要操作的区间;
为了方便加入两个哨兵节点;
注意splay和线段树不同,每个节点都代表了一个元素;
对于本题来说,因为有可能是修改成0或负数,所以tag=1表示执行了修改操作而不是修改成什么;
下传标记修改会覆盖查询,无论时间先后。
Code
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath> using namespace std; #define lc t[x].ch[0]
#define rc t[x].ch[1]
#define pa t[x].fa typedef long long ll;
const int N=5e5, INF=1e9; inline int read()
{
char c=getchar();int x=,f=;
while(c<''||c>''){if(c=='-')f=-; c=getchar();}
while(c>=''&&c<=''){x=x*+c-''; c=getchar();}
return x*f;
} int n, Q, a[N], k, tot;
char s[]; struct meow
{
int ch[], fa, size, v, sum, mx, lm, rm, tag, rev;
meow() {}
meow(int val) {ch[]=ch[]=fa=tag=rev=; size=; v=sum=mx=lm=rm=val;}
}t[N]; int root, sz; inline int wh(int x)
{
return t[pa].ch[]==x;
} int st[N], top; inline int nw()
{
return top ? st[top--] : ++sz;
} inline void paint(int x,int v)
{
t[x].tag=;
t[x].v=v;
t[x].sum=t[x].size*v;
t[x].mx=t[x].lm=t[x].rm=max(t[x].sum,v);
t[x].rev=;
} inline void rever(int x)
{
if(t[x].tag)
return;
t[x].rev^=;
swap(lc,rc);
swap(t[x].lm,t[x].rm);
}
inline void pushDown(int x)
{
if(t[x].rev)
{
if(lc)
rever(lc);
if(rc)
rever(rc);
t[x].rev=;
}
if(t[x].tag)
{
if(lc)
paint(lc,t[x].v);
if(rc)
paint(rc,t[x].v);
t[x].tag=;
}
} inline void update(int x)
{
t[x].size = t[lc].size + t[rc].size + ;
t[x].sum = t[lc].sum + t[rc].sum + t[x].v;
t[x].mx = max(max(t[lc].mx, t[rc].mx), max(, t[lc].rm) + t[x].v + max(, t[rc].lm) );
t[x].lm = max(t[lc].lm, t[lc].sum + t[x].v + max(, t[rc].lm) );
t[x].rm = max(t[rc].rm, t[rc].sum + t[x].v + max(, t[lc].rm) );
} inline void rotate(int x)
{
int f=t[x].fa, g=t[f].fa, c=wh(x);
if(g) t[g].ch[wh(f)]=x; t[x].fa=g;
t[f].ch[c] = t[x].ch[c^]; t[t[f].ch[c]].fa=f;
t[x].ch[c^]=f; t[f].fa=x;
update(f); update(x);
} inline void splay(int x,int tar)
{
for(; pa!=tar; rotate(x))
if(t[pa].fa != tar) rotate(wh(x)==wh(pa) ? pa : x);
if(tar==) root=x;
} void build(int &x,int l,int r,int f)
{
int mid = (l+r)>>;
x=nw(); t[x]=meow(a[mid]); t[x].fa=f;
if(l==r) return;
if(l<mid) build(lc, l, mid-, x);
if(mid<r) build(rc, mid+, r, x);
update(x);
} inline int kth(int k)
{
int x=root, lsize=;
while(x) {
pushDown(x);
int _ = lsize + t[lc].size;
if(k<=_) x=lc;
else if(k<=_+) return x;
else lsize=_+, x=rc;
}
return -;
} void Ins(int k, int tot)
{
for(int i=; i<=tot; i++) a[i]=read();
int f=kth(k+); splay(f, );
int x=kth(k+); splay(x, f);
build(lc, , tot, x);
update(x); update(f);
} void erase(int x)
{
if(!x) return;
st[++top]=x;
erase(lc); erase(rc);
}
void Del(int k, int tot)
{
int f=kth(k); splay(f, );
int x=kth(k+tot+); splay(x, f);
erase(lc); lc=;
update(x); update(f);
} void Mak(int k, int tot)
{
int f=kth(k); splay(f, );
int x=kth(k+tot+); splay(x, f);
paint(lc, read());
update(x); update(f);
} void Rev(int k, int tot)
{
int f=kth(k); splay(f, );
int x=kth(k+tot+); splay(x, f);
rever(lc);
update(x); update(f);
} int Sum(int k, int tot)
{
int f=kth(k); splay(f, );
int x=kth(k+tot+); splay(x, f);
return t[lc].sum;
} int main()
{
n=read(); Q=read();
for(int i=; i<=n+; i++)
a[i]=read();
a[] = a[n+] = -INF;
t[]=meow(-INF); t[].sum=t[].size=;
build(root, , n+, );
for(int i=; i<=Q; i++)
{
scanf("%s",s+);
if(s[]=='X')
printf("%d\n", t[root].mx);
else
{
k=read(); tot=read();
if(s[]=='I') Ins(k, tot);
if(s[]=='D') Del(k, tot);
if(s[]=='M') Mak(k, tot);
if(s[]=='R') Rev(k, tot);
if(s[]=='G') printf("%d\n", Sum(k, tot));
}
}
return ;
}
Time: 2017-07-12