lowbit运算
lowbit(x) = x&(-x)
取x的二进制表示最右边的1和它右边的所有0.结果一定是2的次幂。
例如:
定义
树状数组C仍是一个数组,与前缀和数组sum类似,它是一个用来记录和的数组,只不过它存放的不是前i个整数之和,而是在i号位之前(含i号位)lowbit(i)个整数之和。
C[i]的覆盖长度是lowbit(i),也可以理解成管辖范围。它是2的幂次,如1,2,4,8等。
- 两个操作
getSum(x)返回前x个数之和A[1]+...+A[x]
update(x,v)将第x个数加上v,即A[x]+=v
树状数组的应用
思路:
若使用hash数组的解法,其中hash[x]表示记录整数x当前的出现次数。接着,从右到左遍历序列,如果当前访问的是A[i],就令hash[A[i]]加1,表示当前整数A[i]的出现次数增加了一次。最后,序列中在A[i]右边比A[i]小的数等于hash[1]+hash[2]+...+hash[A[i]-1]的值。这两个工作可以通过update(A[i],1)和getSum(A[i]-1)来解决。
const int max_n = 100010; #define lowbit(i) ((i)&(-i)) int c[max_n]; class Solution { public: void update(int x,int v) { for(int i=x;i<max_n;i+=lowbit(i)){ c[i]+=v; } } int getSum(int x){ int sum = 0; for(int i=x;i>0;i-=lowbit(i)){ sum+=c[i]; } return sum; } vector<int> countSmaller(vector<int>& nums) { vector<int>ans; if(nums.empty()) return ans; ans.resize(nums.size()); memset(c,0,sizeof(c)); // 离散化 vector<int>xs; for(int i=0;i<nums.size();++i) xs.push_back(nums[i]); sort(xs.begin(),xs.end()); auto e = unique(xs.begin(),xs.end()); for(int i=0;i<nums.size();++i) nums[i] = lower_bound(xs.begin(),e,nums[i])-xs.begin()+1; for(auto t:nums) cout<<t<<" "; cout<<endl; for(int i=nums.size()-1;i>=0;--i) { update(nums[i],1); ans[i] = getSum(nums[i]-1); } return ans; } };
其他技巧:
如果要求数组下标在区间[x,y]内的数之和,即A[x]+A[x+1]+...A[y]可以转换成getSum(y)-getSum(x-1)来解决。
问题2
法一 树状数组
class Solution { public: vector<int> c; int N; int lowbit(int i) { return i & (-i); } void update(int x,int v) { for(int i=x;i<N;i+=lowbit(i)){ c[i]+=v; } } int getSum(int x) { int res = 0; for(int i=x;i>0;i-=lowbit(i)) res += c[i]; return res; } int reversePairs(vector<int>& nums) { // 离散化为排名 set<int> s(nums.begin(), nums.end()); map<int, int> m; for (auto x : s) { m[x] = m.size() + 1; } vector<int> ranks(nums.size(), 0); for (int i = 0; i < ranks.size(); ++i) { ranks[i] = m[nums[i]]; } // 初始化树状数组 N = ranks.size() + 1; c = vector<int>(N + 1, 0); // 从后向前遍历查询更新,并获取结果 int res = 0; for (int i = ranks.size() - 1; i >= 0; --i) { // 确定只有一半大小的数的排名 int t = (nums[i] > 0) ? ((nums[i] - 1) / 2) : (nums[i] / 2 - 1); auto it = m.lower_bound(t+1); // 统计小于这个排名的数 if (it != m.begin()) { res += getSum((--it)->second); } // 新增这个数的排名 update(ranks[i], 1); } return res; } };
法二 归并排序
思路 将翻转对的统计视为归并排序完成的一个附加任务。
class Solution { public: void mergeSort(vector<int>& nums,int left,int right,int& ans) { if(left==right) return; int mid = (left+right)>>1; mergeSort(nums,left,mid,ans); mergeSort(nums,mid+1,right,ans); merge(nums,left,right,ans); } void merge(vector<int>& nums,int left,int right,int& ans) { int mid = (left+right)>>1; int i = left; int j = mid+1; int m = left; int n = mid+1; while(m<=mid&&n<=right) { if(m<=mid&&nums[m]>2LL*nums[n]) { ans+=(mid-m+1); ++n; } else ++m; } vector<int>tmp; while(i<=mid&&j<=right) { if(nums[i]<=nums[j]) { tmp.push_back(nums[i++]); } else { tmp.push_back(nums[j++]); } } while(i<=mid) tmp.push_back(nums[i++]); while(j<=right) tmp.push_back(nums[j++]); int k = 0; for(int i=left;i<=right;++i) nums[i]=tmp[k++]; } int reversePairs(vector<int>& nums) { if(nums.empty()) return 0; int ans = 0; mergeSort(nums,0,nums.size()-1,ans); return ans; } };