1.分块算法 (n*sqrt(n)*log^2n) 3200+ms
给出n个数,有两种操作,一种是修改某个数的值,另一种查询指定区间第k大。
比较快的做法是树套树,而分块算法复杂度比较高写起来方便。分块算法可以很简单的处理单独修改某个值的情况。
将n个数分成num块,每块大小siz=n/num。每一个块内部进行排序,查询[l,r]第k大时,先二分答案,对于完全包含在区间的内块直接二分搜索,而对于区间两端只有部分包含的则直接遍历查找。复杂度是logn*(2*siz+num*log(siz)),这里取siz=sqrt(n),复杂度主要就是logn*sqrt(n)*logn。
对于修改,因为一个数只在一个块里,直接在那个块里进行修改,原本所在块是已排序的,修改后只有一个数不满足顺序,做一次冒泡就好了,复杂为siz=sqrt(n)。
#include
#include
#include
#include
#include
#include
#include
#include
2.线段树/树状数组套平衡树 O(n*log^3n) 700+ms sqrt(n)和logn还是差的挺远的
每个区间节点维护的是一棵平衡树,平衡树维护的是对应区间内的所有数。
查询的时候同样是二分答案mid,找到[l,r]对应所有区间节点,在每个节点的平衡树上查询<=mid的数有多少个,复杂度二分+线段树+平衡树=O(logn*logn*logn)。
修改的时候找到对应区间删除原来的数,再插入新的数即可 O(logn*logn)。
这种做法满足区间和,所以线段树可以用树状数组替换会比较快。
#include
#include
#include
#include
#include
using namespace std;
const int maxn=5e4+10;
struct SBT
{
int left,right,size,key;
void Init()
{
left=right=0;
size=1;
}
}tree[20*maxn];
int tot;
void left_rotate(int &x)//左旋
{
int y=tree[x].right;
tree[x].right=tree[y].left;
tree[y].left=x;
tree[y].size=tree[x].size;
tree[x].size=tree[tree[x].left].size+tree[tree[x].right].size+1;
x=y;
}
void right_rotate(int &x)//右旋
{
int y=tree[x].left;
tree[x].left=tree[y].right;
tree[y].right=x;
tree[y].size=tree[x].size;
tree[x].size=tree[tree[x].left].size+tree[tree[x].right].size+1;
x=y;
}
void maintain(int &x,int flag)
{
if(flag==0)
{
if(tree[tree[tree[x].left].left].size > tree[tree[x].right].size)
right_rotate(x);
else if(tree[tree[tree[x].left].right].size > tree[tree[x].right].size)
left_rotate(tree[x].left),right_rotate(x);
else return;
}
else
{
if(tree[tree[tree[x].right].right].size > tree[tree[x].left].size)
left_rotate(x);
else if(tree[tree[tree[x].right].left].size > tree[tree[x].left].size)
right_rotate(tree[x].right),left_rotate(x);
else return;
}
maintain(tree[x].left,0);
maintain(tree[x].right,1);
maintain(x,0);
maintain(x,1);
}
//插入元素,相同元素放在右子树中
void insert(int &x,int key)
{
if(x==0)
{
x=++tot;
tree[x].Init();
tree[x].key=key;
}
else
{
tree[x].size++;
if(key=tree[x].key);
}
}
//删除key值的元素
int del(int &x,int key)
{
if(!x)return 0;
tree[x].size--;
if(key==tree[x].key || (keytree[x].key&&tree[x].right==0))
{
if(tree[x].left && tree[x].right)
{
int p=del(tree[x].left,key+1);
tree[x].key=tree[p].key;
return p;
}
else
{
int p=x;
x=tree[x].left+tree[x].right;
return p;
}
}
else return del(key>t;
while(t--){
scanf("%d%d", &n, &m);
init();
while(m--){
char s[5]; int b,c,d;
scanf("%s%d%d", s, &b, &c);
if(s[0]=='Q'){
scanf("%d", &d);
int l=1,r=1e9+1,ans;
while(l>1;
int cnt=sum(c, mid)-sum(b-1, mid);
//cout<=d){
ans=mid;
r=mid;
}
else l=mid+1;
}
printf("%d\n", ans);
}
else{
bit_del(b, a[b]);
bit_add(b, c);
a[b]=c;
}
}
}
return 0;
}
这种做法跟第二种刚好相反,线段树是按值建的,节点[l,r]是指满足值>=l && <=r。平衡树维护的是数对应的数组下标。先将所有可能用到的值(包含初始值和修改的值)读入,然后离散化建立线段树,对于a[i](离散化后的),找到所有包含a[i]也就是l<=a[i]<=r的区间节点,将i插入到对应的平衡树中。
查询[ll,rr]的第k大的时候,对于线段树区间[l,r],先查询左孩子[l,mid]中的平衡树查找在[ll,rr]范围内的数有cnt个,假如cnt<=k,则直接递归到左孩子。cnt>k,递归到右孩子查询第k-cnt大(因为左孩子已经包含最小的cnt个了)。思想相当于整体二分吧,复杂度O(logn*logn)。
修改和2类似,复杂度O(logn*logn)
#include
#include
#include
#include
#include
using namespace std;
const int maxn=5e4+10;
struct Query
{
int l, r, k;
int kind;
};
Query q[maxn];
struct SBT
{
int left,right,size,key;
void Init()
{
left=right=0;
size=1;
}
}tree[20*maxn];
int tot;
void left_rotate(int &x)//左旋
{
int y=tree[x].right;
tree[x].right=tree[y].left;
tree[y].left=x;
tree[y].size=tree[x].size;
tree[x].size=tree[tree[x].left].size+tree[tree[x].right].size+1;
x=y;
}
void right_rotate(int &x)//右旋
{
int y=tree[x].left;
tree[x].left=tree[y].right;
tree[y].right=x;
tree[y].size=tree[x].size;
tree[x].size=tree[tree[x].left].size+tree[tree[x].right].size+1;
x=y;
}
void maintain(int &x,int flag)
{
if(flag==0)
{
if(tree[tree[tree[x].left].left].size > tree[tree[x].right].size)
right_rotate(x);
else if(tree[tree[tree[x].left].right].size > tree[tree[x].right].size)
left_rotate(tree[x].left),right_rotate(x);
else return;
}
else
{
if(tree[tree[tree[x].right].right].size > tree[tree[x].left].size)
left_rotate(x);
else if(tree[tree[tree[x].right].left].size > tree[tree[x].left].size)
right_rotate(tree[x].right),left_rotate(x);
else return;
}
maintain(tree[x].left,0);
maintain(tree[x].right,1);
maintain(x,0);
maintain(x,1);
}
//插入元素,相同元素放在右子树中
void insert(int &x,int key)
{
if(x==0)
{
x=++tot;
tree[x].Init();
tree[x].key=key;
}
else
{
tree[x].size++;
if(key=tree[x].key);
}
}
//删除key值的元素
int del(int &x,int key)
{
if(!x)return 0;
tree[x].size--;
if(key==tree[x].key || (keytree[x].key&&tree[x].right==0))
{
if(tree[x].left && tree[x].right)
{
int p=del(tree[x].left,key+1);
tree[x].key=tree[p].key;
return p;
}
else
{
int p=x;
x=tree[x].left+tree[x].right;
return p;
}
}
else return del(key>1, lc=rt<<1, rc=rt<<1|1;
void seg_insert(int rt, int l, int r, int p, int v)
{
insert(root[rt], v);
if(l==r)
return;
getc;
if(p<=m)
seg_insert(lc, l, m, p, v);
else seg_insert(rc, m+1, r, p, v);
}
void seg_del(int rt, int l, int r, int p, int v)
{
del(root[rt], v);
if(l==r)
return;
getc;
if(p<=m)
seg_del(lc, l, m, p, v);
else seg_del(rc, m+1, r, p, v);
}
int query(int rt, int l, int r, int ll, int rr, int k)
{
if(l==r)
return l;
getc;
int cnt=get_kth(root[lc], rr)-get_kth(root[lc],ll-1);
if(cnt>=k)
return query(lc, l, m, ll, rr, k);
else return query(rc, m+1, r, ll, rr, k-cnt);
}
int num=0;
void init()
{
tot=0;
memset(root, 0, sizeof(root));
tree[0].Init(); tree[0].size=0;
memset(root, 0, sizeof(root));
scanf("%d", &m);
num=0;
for(int i=1; i<=n; i++){
scanf("%d", a+i);
h[num++]=a[i];
}
for(int i=0; i>t;
while(t--&&cin>>n){
init();
for(int i=0; i
上面三种做法一种比一种快,但是代码也越来越复杂。。。。
还有两种做法,但是还没学会。。。树状数组套主席树和cdq分治也可以做,cdq分治应该是时间和空间都最优的