题解 P5356 【[Ynoi2017]由乃打扑克】

rui_er

2020-09-18 21:19:37

Solution

区间加区间第 $k$ 小问题。 ~~碰到 Ynoi 题,先考虑分块。~~ ←暴论,但这题就是分块。 --- 由于要求第 $k$ 小,我们将原数列分成若干块(这里我们块长为 $400$),每块内先进行排序。 下面分别考虑两个操作: - 操作一:查询区间第 $k$ 小。 我们取两端点为区间内最大、最小值,进行二分。对于 $mid$,我们求出区间内有多少个**小于等于** $mid$ 的数。具体做法就是,对于零散块暴力统计,对于整块进行块内二分。对于每次二分,如果这个个数**大于等于** $k$,我们就可以更新答案,最后输出即可。 求出最大、最小值后,我们可以进行一点优化: 1. 对于 $k=1$,直接输出最小值,不用二分。 1. 对于 $k=r-l+1$,直接输出最大值,不用二分。 1. 特判 $k\lt 1$ 和 $k\gt r-l+1$,输出 $-1$。 - 操作二:区间加 $k$。 对于左侧零散块,我们暴力更新原数组和排序后的数组,由于后者有更新,因此将其按照是否在更改区间内分类,分别压入两个数组,然后对这两个数组进行归并。右侧零散块同理。 对于整块,暴力更新太费时,想到线段树的懒标记,我们这里也给每个块搞一个统计整块修改的 tag,直接更新它即可。注意查询时也要加上这个值,我因为有一处忘加这个 tag,调了几个小时,也可能是我太菜了吧。 ![](https://cdn.luogu.com.cn/upload/image_hosting/v638sy13.png) $$\small\color{gray}\text{调试过程中一次对拍的结果}$$ 最终我们就得到了正解。 --- 总结一下:我们通过分块,统计懒标记,对块内归并来维护块内有序性,然后根据分块进行统计答案即可。 --- 主要代码: ```cpp struct Node { int val, pos; Node() {} Node(int a, int b) : val(a), pos(b) {} ~Node() {} bool operator < (const Node &a) const {return val < a.val;} }cp[N], mer1[SIZE], mer2[SIZE]; int L[K], R[K], tag[K]; void init() { scanf("%d%d", &n, &m); for(int i=1;i<=n;i++) { scanf("%d", &a[i]); cp[i] = Node(a[i], i); } } #define whichBlock(x) (\ (x - 1) / SIZE + 1\ ) void initBlock() { tot = whichBlock(n); for(int i=1;i<=tot;i++) { L[i] = R[i-1] + 1; R[i] = SIZE * i; sort(cp+L[i], cp+R[i]+1); } R[tot] = n; } void getBorder(int l, int r, int &_l, int &_r) { int bL = whichBlock(l), bR = whichBlock(r); if(bL == bR) { for(int i=l;i<=r;i++) { _l = min(_l, a[i]+tag[bL]); _r = max(_r, a[i]+tag[bL]); } } else { for(int i=l;i<=R[bL];i++) { _l = min(_l, a[i]+tag[bL]); _r = max(_r, a[i]+tag[bL]); } for(int i=L[bR];i<=r;i++) { _l = min(_l, a[i]+tag[bR]); _r = max(_r, a[i]+tag[bR]); } for(int i=bL+1;i<bR;i++) { _l = min(_l, cp[L[i]].val+tag[i]); _r = max(_r, cp[R[i]].val+tag[i]); } } } int getNumbersLeqK(int l, int r, int k) { int res = 0; int bL = whichBlock(l), bR = whichBlock(r); if(bL == bR) for(int i=l;i<=r;i++) res += (a[i] + tag[bL] <= k); else { for(int i=l;i<=R[bL];i++) res += (a[i] + tag[bL] <= k); for(int i=L[bR];i<=r;i++) res += (a[i] + tag[bR] <= k); for(int i=bL+1;i<bR;i++) { int mi = L[i], ma = R[i]; if(cp[mi].val + tag[i] > k) continue; if(cp[ma].val + tag[i] <= k) { res += ma - mi + 1; continue; } while(mi < ma) { int mid = ((mi + ma) >> 1) + 1; if(cp[mid].val + tag[i] <= k) mi = mid; else ma = mid - 1; } if(cp[mi].val + tag[i] <= k) res += mi - L[i] + 1; } } return res; } int query(int l, int r, int k) { int mi = inf, ma = -inf; getBorder(l, r, mi, ma); if(k == 1) return mi; if(k == r - l + 1) return ma; if(k < 1 || k > r - l + 1) return -1; int res = -1; while(mi <= ma) { int mid = (mi + ma) >> 1; if(getNumbersLeqK(l, r, mid) < k) mi = mid + 1; else ma = (res=mid) - 1; } return res; } void modify(int l, int r, int k) { int bL = whichBlock(l), bR = whichBlock(r); int tp1, tp2; tp1 = tp2 = 0; for(int i=L[bL];i<=R[bL];i++) { if(i >= l && i <= r) a[i] += k; if(cp[i].pos >= l && cp[i].pos <= r) mer1[++tp1] = Node(cp[i].val+k, cp[i].pos); else mer2[++tp2] = cp[i]; } int u1, u2; u1 = u2 = 1; int u = L[bL]; while(u <= R[bL]) { // mergesort if(u1 <= tp1 && (u2 > tp2 || mer1[u1].val < mer2[u2].val)) cp[u++] = mer1[u1++]; else cp[u++] = mer2[u2++]; } if(bL != bR) { int tp1, tp2; tp1 = tp2 = 0; for(int i=L[bR];i<=R[bR];i++) { if(i >= l && i <= r) a[i] += k; if(cp[i].pos >= l && cp[i].pos <= r)mer1[++tp1] = Node(cp[i].val+k, cp[i].pos); else mer2[++tp2] = cp[i]; } int u1, u2; u1 = u2 = 1; int u = L[bR]; while(u <= R[bR]) { // mergesort if(u1 <= tp1 && (u2 > tp2 || mer1[u1].val < mer2[u2].val))cp[u++] = mer1[u1++]; else cp[u++] = mer2[u2++]; } for(int i=bL+1;i<bR;i++) tag[i] += k; } } ```