您的位置:首页 > 产品设计 > UI/UE

HDU 5412 CRB and Queries (整体二分)

2015-08-25 10:49 453 查看
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5412

题目大意:对一个数组A
(N<= 10^5)进行两种操作:

  1 l v:将第A[l]的值修改成v

  2 l r k:求l~r区间的第k小值

操作总数为10^5.

2015年多校第十场的题目,当时乱套主席树什么的模板,发现并不能过,赛后学习了一种叫做整体二分的方法,感觉很是厉害。

整体二分是二分答案。大致方法如下:

  1、先把所有对数组的操作保存起来,包括赋值。

  2、从0~inf二分一个数值,并以此数值为界,将操作划分成两部分

  3、递归处理

细节有点不好说,还是看代码比较清楚。

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
#define N 150100
#define inf 1000000100
struct Que{
int tp;
int x, y, k, id, cur;
}Q[N<<1], Q1[N<<1], Q2[N<<1];
int qcnt;
int a
;

int tr
;
int ans
;
int m;
inline void add(int id, int v){
for(int i = id; i < N; i += (i&-i))
tr[i] += v;
}
inline int sum(int id){
int ret = 0;
for(int i = id; i > 0; i -= (i&-i))
ret += tr[i];
return ret;
}

int tmp
;
void divide(int s, int t, int l, int r)
{
if(s > t) return;
if(l == r)
{
for(int i = s; i <= t; i++)
if(Q[i].tp == 2)
ans[Q[i].id] = l;
return;
}
int mid = (l+r)>>1;
for(int i = s; i <= t; i++)
{
if(Q[i].tp == 0 && Q[i].y <= mid) add(Q[i].x, 1);
else if(Q[i].tp == 1 && Q[i].y <= mid) add(Q[i].x, -1);
else if(Q[i].tp == 2) tmp[Q[i].id] = sum(Q[i].y) - sum(Q[i].x-1);
}
//还原
for(int i = s; i <= t; i++)
{
if(Q[i].tp == 0 && Q[i].y <= mid) add(Q[i].x, -1);
else if(Q[i].tp == 1 && Q[i].y <= mid) add(Q[i].x, 1);
}
//下面是根据当前二分的答案mid,将操作划分成两部分
int l1 = 0, l2 = 0;
for(int i = s; i <= t; i++)
{
if(Q[i].tp == 2){
if(Q[i].cur+tmp[Q[i].id] >= Q[i].k) Q1[l1++] = Q[i];
else {
Q[i].cur += tmp[Q[i].id];
Q2[l2++] = Q[i];
}
}
else{
if(Q[i].y <= mid) Q1[l1++] = Q[i];
else Q2[l2++] = Q[i];
}
}
for(int i = 0; i < l1; i++) Q[s+i] = Q1[i];
for(int i = 0; i < l2; i++) Q[s+l1+i] = Q2[i];
divide(s, s+l1-1, l, mid);
divide(s+l1, t, mid+1, r);
}

int main()
{
int n, q;
while(~scanf("%d", &n))
{
qcnt = 0;
int Max = -1;
int Min = inf;
for(int i = 1; i <= n; i++){
scanf("%d", &a[i]);
Max = max(Max, a[i]);
Min = min(Min, a[i]);
//赋值也要看成是操作
Q[qcnt].x = i, Q[qcnt].y = a[i], Q[qcnt].tp = 0;
Q[qcnt++].cur = 0;
}
scanf("%d", &q);
int id = 0;
for(int i = 0; i < q; i++)
{
int op, x, y, k;
scanf("%d", &op);
if(op == 1){
scanf("%d %d", &x, &y);
//to == 1表示这个值是要被修改的,减去当前值,再加上新值,就完成了修改
//cur的作用:若当前的答案二分区间是 [l, r],则cur保存在Q[i].l, Q[i].r范围之内,有多少个小于 l 的数
Q[qcnt].x = x, Q[qcnt].y = a[x], Q[qcnt].tp = 1, Q[qcnt++].cur = 0;
Q[qcnt].x = x, Q[qcnt].y = y, Q[qcnt].tp = 0, Q[qcnt++].cur = 0;
a[x] = y;
Max = max(Max, y);
Min = min(Min, y);
}
else {
scanf("%d %d %d", &x, &y, &k);
Q[qcnt].x = x, Q[qcnt].y = y, Q[qcnt].k = k, Q[qcnt].tp = 2;
Q[qcnt].id = id++, Q[qcnt++].cur = 0;
}
}
//树状数组每次add后还会被还原,就没必要每次初始化了
//memset(tr, 0, sizeof(tr));
divide(0, qcnt-1, Min, Max);
for(int i = 0; i < id; i++)
printf("%d\n", ans[i]);
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: