HDU 5412 CRB and Queries (整体二分)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5412
题目大意:对一个数组A[N](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[N]; int tr[N]; int ans[N]; 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[N]; 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; }