题目链接:ZOJ2112
【题意】可以单点修改的区间第k大。
【分析】这题有三种方法可以做:1.分块+二分(最好写)q*sqrt(n)*logn*logn 2.线段树套Treap+二分q*logn*logn*logn 3.树状数组+主席树q*logn*logn;
首先第一种:分块+二分。把区间分块,每个块排序,然后二分第k大的值域即可。
【AC CODE】1590ms O(q*sqrt(n)*logn*logn)
#include <cstdio>
#include <cstring>
#include <cctype>
#include <cmath>
#include <set>
//#include <unordered_set>
#include <queue>
#include <stack>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
typedef long long LL;
#define rep(i,a,n) for(int i = a; i < n; i++)
#define repe(i,a,n) for(int i = a; i <= n; i++)
#define per(i,n,a) for(int i = n; i >= a; i--)
#define clc(a,b) memset(a,b,sizeof(a))
const int INF = 0x3f3f3f3f, MAXN = 50000+10, SIZE = 305;
int block[MAXN/SIZE+10][SIZE], cnt, len[MAXN/SIZE+10], a[MAXN], mi, mx;
void update(int p, int v)//logn*SIZE
{
int x = p/SIZE;
int c = lower_bound(block[x],block[x]+len[x],a[p])-block[x];
block[x][c] = a[p] = v;
sort(block[x],block[x]+len[x]);
mx = max(mx,v), mi = min(mi,v);
}
int tmp[SIZE<<1],tol;
bool find_k(int st, int ed, int v, int kk)
{
int sum = upper_bound(tmp,tmp+tol,v)-tmp;
if(sum >= kk) return true;
rep(i,st+1,ed)
{
sum += upper_bound(block[i],block[i]+len[i],v)-block[i];
if(sum >= kk) return true;
}
return false;
}
int query(int x, int y, int k)
{
int bx = x/SIZE, by = y/SIZE;
tol = 0;
if(bx == by)//SIZE*logn
{
repe(i,x,y) tmp[tol++] = a[i];
sort(tmp,tmp+tol);
return tmp[k-1];
}
rep(i,x%SIZE,len[bx]) tmp[tol++] = a[bx*SIZE+i];
repe(i,0,y%SIZE) tmp[tol++] = a[by*SIZE+i];
sort(tmp,tmp+tol);//SIZE*logn
int l = mi,r = mx, ans = -1;
while(l <= r)
{
int m = (l+r)>>1;
if(find_k(bx,by,m,k))
{
ans = m;
r = m-1;
}
else l = m+1;
}
return ans;
}
int main()
{
#ifdef SHY
freopen("d:\\1.txt", "r", stdin);
#endif
int t;
scanf("%d%*c", &t);
while(t--)
{
int n,q;
scanf("%d %d", &n, &q);
cnt = 0;
clc(len,0);
mx = -1, mi = INF;
rep(i,0,n)
{
scanf("%d", &a[i]);
block[cnt][len[cnt]++] = a[i];
mx = max(mx,a[i]);
mi = min(mi,a[i]);
if(len[cnt] == SIZE) cnt++;
}
rep(i,0,cnt)
sort(block[i],block[i]+len[i]);
char op[10];
while(q--)
{
scanf("%s", op);
if('C' == op[0])
{
int p,v;
scanf("%d %d%*c", &p, &v);
update(p-1,v);
}
else
{
int x,y,k;
scanf("%d %d %d%*c",&x, &y,&k);
printf("%d\n", query(x-1,y-1,k));
}
}
}
return 0;
}
第二种:线段树套Treap,这个也很好理解,每个线段树结点套一个Treap维护这个结点所表示的区间的所有值,然后和分块一样二分第k大的值域即可。
【AC CODE】1110ms O(q*logn*logn*logn)
#include <cstdio>
#include <cstring>
#include <cctype>
#include <cmath>
#include <set>
//#include <unordered_set>
#include <queue>
#include <stack>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
typedef long long LL;
#define rep(i,a,n) for(int i = a; i < n; i++)
#define repe(i,a,n) for(int i = a; i <= n; i++)
#define per(i,n,a) for(int i = n; i >= a; i--)
#define clc(a,b) memset(a,b,sizeof(a))
const int INF = 0x3f3f3f3f, MAXN = 50000+10;
struct NODE{
NODE *ch[2];
int r, v, sum, cnt;
}node[MAXN*20];
int a[MAXN], mi, mx, tol;
NODE *rt[MAXN<<1];
int cmp(const NODE *a, int x){
if(x == a->v) return -1;
return x<a->v?0:1;
}
void push_up(NODE *u){
u->sum = u->cnt;
if(u->ch[0]) u->sum += u->ch[0]->sum;
if(u->ch[1]) u->sum += u->ch[1]->sum;
}
NODE *newnode(int a){
node[tol].v = a,node[tol].ch[0] = node[tol].ch[1] = NULL, node[tol].r = rand();
node[tol].sum = node[tol].cnt = 1;
return &node[tol++];
}
void clear(NODE* &u)//清空树
{
u = NULL;
}
void rotate(NODE* &u, int d)//旋转,d = 0左旋,d = 1右旋
{
NODE *k = u->ch[d^1];
u->ch[d^1] = k->ch[d], k->ch[d] = u;
push_up(u), push_up(k); //更新sum,先更新u
u = k;
}
void insert(NODE* &u, int v)//插入键值v(不需要保证v是否存在)
{
if(!u) u = newnode(v);
else
{
int d = cmp(u,v);//不要使用cmp(),可能已经存在相同的v
if(-1 == d)//已经存在该点
{
u->cnt++;
u->sum++;
return;
}
insert(u->ch[d], v);
if(u->r < u->ch[d]->r) //如果儿子的r比当前大则需要旋转
rotate(u,d^1);//左儿子则右旋,右儿子左旋
}
push_up(u);//更新sum
}
void del(NODE* &u, int v)//删除键值v
{
int d = cmp(u,v);
if(~d)//没找到继续往下找
del(u->ch[d],v);
else
{
if(u->cnt > 1)
{
u->cnt--;
u->sum--;
return;
}
NODE *o = u;
if(u->ch[0] && u->ch[1])//左右儿子都存在
{
int d2 = u->ch[0]->r > u->ch[1]->r?1:0;//优先级大的儿子旋转为根
rotate(u,d2);
del(u->ch[d2],v);
}
else
{
if(!u->ch[0]) u = u->ch[1];//只有右儿子
else u = u->ch[0];//只有左儿子
//delete o;
//u = NULL;
}
}
if(u) push_up(u);
}
int upper(NODE *u, int v)//v在树中排第几,即有几个<=v
{
if(!u) return 0;
if(v == u->v) return u->cnt+(u->ch[0]?u->ch[0]->sum:0);
if(v < u->v) return upper(u->ch[0], v);
return upper(u->ch[1],v)+(u->ch[0]?u->ch[0]->sum:0)+u->cnt;
}
void pt(NODE *u)
{
if(NULL == u) return;
pt(u->ch[0]);
rep(i,0,u->cnt)
printf("%d\n", u->v);
pt(u->ch[1]);
}
inline int id(int x, int y){return x+y|x!=y;}
void bulid(int x, int y)
{
int u = id(x,y);
clear(rt[u]);
repe(i,x,y) insert(rt[u],a[i]);
if(x == y) return;
int m = (x+y)>>1;
bulid(x,m);
bulid(m+1,y);
}
int tmp;
void update(int x, int y, int p, int v)
{
if(x == y)
{
int u = id(x,y);
tmp = rt[u]->v;
rt[u]->v = v;
return;
}
int m = (x+y)>>1;
if(p <= m) update(x,m,p,v);
else update(m+1,y,p,v);
int u = id(x,y);
del(rt[u],tmp);
insert(rt[u],v);
}
int query(int x, int y, int ql, int qr, int v)
{
if(ql <= x && y <= qr)
{
int u = id(x,y);
return upper(rt[u],v);
}
int m = (x+y)>>1, ans = 0;
if(ql <= m) ans += query(x,m,ql,qr,v);
if(qr > m) ans += query(m+1,y,ql,qr,v);
return ans;
}
int n;
int sloved(int x, int y, int k)
{
int l = mi, r = mx, ans;
while(l <= r)
{
int m = (l+r)>>1;
if(query(0,n-1,x,y,m) >= k)
ans = m, r = m-1;
else l = m+1;
}
return ans;
}
int main()
{
#ifdef SHY
freopen("d:\\1.txt", "r", stdin);
#endif
int t;
scanf("%d%*c", &t);
while(t--)
{
int q;
scanf("%d %d%*c", &n, &q);
mi = INF, mx = -1;
tol = 0;
rep(i,0,n)
{
scanf("%d%*c", &a[i]);
mi = min(mi,a[i]);
mx = max(mx,a[i]);
}
bulid(0,n-1);
while(q--)
{
char op[10];
scanf("%s",op);
if('C' == op[0])
{
int p,v;
scanf("%d %d", &p,&v);
p--;
update(0,n-1,p,v);
mi = min(mi,v);
mx = max(mx,v);
}
else
{
int x,y,k;
scanf("%d %d %d",&x, &y, &k);
x--,y--;
printf("%d\n", sloved(x,y,k));
}
}
}
return 0;
}
第三种:树状数组+主席树,这个是刚刚学的,觉得好神奇,还是看这个博客讲得比较好。
http://blog.finaltheory.info/algorithm/Chairman-Tree.html#content-heading
【AC CODE】120ms O(q*logn*logn)
#include <cstdio>
#include <cstring>
#include <cctype>
#include <cmath>
#include <set>
//#include <unordered_set>
#include <queue>
#include <stack>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
typedef long long LL;
#define rep(i,a,n) for(int i = a; i < n; i++)
#define repe(i,a,n) for(int i = a; i <= n; i++)
#define per(i,n,a) for(int i = n; i >= a; i--)
#define clc(a,b) memset(a,b,sizeof(a))
const int INF = 0x3f3f3f3f, MAXN = 50000+10, MAXQ = 10000+10,MAXM = MAXN*30;
struct IN{
bool f;
int a,b,k;
}in[MAXQ];
int lc[MAXM], rc[MAXM], sum[MAXM], tol;//主席树内存池
int num[MAXN+MAXQ], sot[MAXN+MAXQ], cnt;//离散
int rt[MAXN<<1], a[MAXN], n;//rt[]保存各个版本的线段树根,rt[1~n]保存树状数组套的主席树,后面的保存初始的n个前缀主席树
inline int mhash(int v){return lower_bound(sot+1,sot+1+cnt,v)-sot;}
void init_hash(int cc)
{
sort(num+1,num+1+cc);
sot[1] = num[1];
cnt = 1;
repe(i,2,cc) if(num[i] != num[i-1]) sot[++cnt] = num[i];
}
inline int newnode(int _lc, int _rc, int _sum)
{
lc[tol] = _lc, rc[tol] = _rc, sum[tol] = _sum;
return tol++;
}
void bulid(int &u, int x, int y, int p)//在前一个版本基础上添加一个新的结点v,成为一棵新树
{
u = newnode(lc[u],rc[u],sum[u]+1);
if(x == y) return;
int m = (x+y)>>1;
if(p <= m) bulid(lc[u],x,m,p);
else bulid(rc[u],m+1,y,p);
}
void t_update(int &u, int x, int y, int p, int v)//树状数组中每个结点所套的主席树,更新到才新建,省内存
{
if(!u) u = newnode(0,0,0);
sum[u] += v;
if(x == y) return;
int m = (x+y)>>1;
if(p <= m) t_update(lc[u],x,m,p,v);
else t_update(rc[u],m+1,y,p,v);
}
/*树状数组记录区间[1,n]每个数字出现的次数,次数用值域主席树记录*/
inline int lowbit(int x){return x&-x;}
void add(int x, int v)//树状数组添加或删除元素a[x]
{
int p = a[x];
while(x <= n)
{
t_update(rt[x],1,cnt,p,v);
x += lowbit(x);
}
}
int tx[50], ty[50], len[2];//查询用到的所有树
int t_query(int x, int y, int k)
{
if(x == y) return x;
int d = 0;
rep(i,0,len[1]) d += sum[lc[ty[i]]];
rep(i,0,len[0]) d -= sum[lc[tx[i]]];
int m = (x+y)>>1;
if(d >= k)
{
rep(i,0,len[1]) ty[i] = lc[ty[i]];
rep(i,0,len[0]) tx[i] = lc[tx[i]];
return t_query(x,m,k);
}
rep(i,0,len[1]) ty[i] = rc[ty[i]];
rep(i,0,len[0]) tx[i] = rc[tx[i]];
return t_query(m+1,y,k-d);
}
/*====== 调用操作 ======*/
void init(int cc)//logn*n新建初始前缀树
{
init_hash(cc);
repe(i,1,n) a[i] = mhash(a[i]);//把所有a[i]变成离散值
tol = 1;
clc(rt,0);
rt[0] = sum[0] = lc[0] = rc[0] = 0;//只需要用一个结点代表第0颗树
repe(i,1,n)
{
rt[i+n] = rt[i+n-1];
bulid(rt[i+n],1,cnt,a[i]);
}
}
void update(int p, int v)//更新a[p] -> v
{
add(p,-1);
a[p] = mhash(v);
add(p,1);
}
int query(int x, int y, int k)//查询区间[x,y]的第k大元素
{
/*把所有表示区间y的树减去区间x-1的树存起来查询*/
len[0] = len[1] = 1;
tx[0] = rt[x-1?x-1+n:0], ty[0] = rt[y+n];//先把初始的一棵树加进去
for(int i = x-1; i > 0; i -= lowbit(i)) tx[len[0]++] = rt[i];//树状数组求区间[x,y]和就是sum(y)-sum(x-1)则,把多个这样的点一起在主席树中算就可以了
for(int i = y; i > 0; i -= lowbit(i)) ty[len[1]++] = rt[i];
return t_query(1,cnt,k);
}
int main()
{
#ifdef SHY
freopen("d:\\1.txt", "r", stdin);
#endif
int t;
scanf("%d", &t);
while(t--)
{
int q;
scanf("%d %d", &n, &q);
int cc = 0;//所有值没去重的数量
repe(i,1,n) scanf("%d", &a[i]), num[++cc] = a[i];
char op[10];
rep(i,0,q)
{
scanf("%s", op);
if('Q' == op[0])
{
in[i].f = 0;
scanf("%d %d %d",&in[i].a, &in[i].b, &in[i].k);
}
else
{
in[i].f = 1;
scanf("%d %d", &in[i].a, &in[i].b);
num[++cc] = in[i].b;
}
}
init(cc);
rep(i,0,q)
{
if(!in[i].f) printf("%d\n", sot[query(in[i].a,in[i].b,in[i].k)]);
else update(in[i].a,in[i].b);
}
}
return 0;
}