具体说下二分 二分是在一个有单调性的序列上进行查询的算法 一般有两类查询要求 一类是给一个定值x,找到序列中的第一个数k,使得k >= x,即x后面紧挨的一个数(后继) 第二类是找到一个k,使得k最大且 k <= x 二分最难的地方在于边界判断 对于第一类: 设变量L, R 表示当前序列左端和右端,初值为1和n,设 mid = (L + R) / 2(C++自带向下取整)
以mid为分界线将序列分为两半(一般都认为mid属于左半边),若a[mid] >= x,即mid位置的元素符合要求 那么答案所在的区间一定属于[L, mid] ,于是我们使 R = mid 若a[mid] < x,mid位置元素不符合要求, 则答案所属区间一定在[mid+1, R]里面,使L = mid + 1
但是为什么要一定要写判断a[mid] >= x,然后改变R呢?为什么不是写 a[mid] <= x,然后改变L? 因为现在我们有一种办法能不断缩小答案所在的区间,并且那么这个区间在缩小的时候只有一个端点在变化位置,那么我们应该让区间尽量往左边“缩”,才能找到x后面紧挨的数,从而放弃掉那些更大的数
int search(int x) { while(l < r) { int mid = l + r >> 1; // 相当于(l + r) / 2 if(a[mid] >= x) r = mid; else l = mid + 1; } return a[l]; } int search2(int x) { while(l < r) { int mid = l + r + 1 >> 1; //这里+1是很有必要的 if(a[mid] <= x) l = mid; else r = mid - 1; } return a[l]; }具体怎么记忆呢 只需要想,当R = L + 1时,我们要使L == R来作为循环的结束 那么对于第一类,由向下取整,mid为L,此时L = mid + 1或者 R = mid 都能达到 L == R的状态 对于第二类,如果不写mid + l + 1 ,mid就依然取L,但是R = mid - 1就会跳过L == R的状态,或者L = mid 会进入无限循环
简单来说就是L + R 最终取到L L + R + 1 最终取到R
#include <algorithm> #include <iostream> #include <cstdio> using namespace std; const int MAXN = 100000 + 10; #define debug(x) cerr << #x << "=" << x << endl; #define lowbit(x) x&-x int n, a[MAXN], t[MAXN], h[MAXN]; void ins(int k, int pos) { while(pos <= n) { t[pos] += k; pos += lowbit(pos); } } int que(int pos) { int que_sum = 0; while(pos) { que_sum += t[pos]; pos -= lowbit(pos); } return que_sum; } int main() { scanf("%d", &n); for(int i=2; i<=n; i++) { scanf("%d", a + i); } for(int i=1; i<=n; i++) ins(1,i); for(int i=n; i>=1; i--) { int pos = a[i] + 1; int l = 1, r = n; int ans; while(l < r) { int mid = l + r >> 1; int tsum = que(mid); if(tsum >= pos) r = mid; else l = mid + 1; } h[i] = l; ins(-1, h[i]); } for(int i=1; i<=n; i++) { printf("%d\n", h[i]); } return 0; }