1329. Sequence maintenance
There is a sequencea
of length n
, and there are q
operations on the sequence.
Each operation has a queried number bi
, subtracting one from all numbers that greater than or equal to bi
in the sequence.
Returns how many numbers are subtracted by one after each operation.
Example
Example1
Input
4
3
[1,2,3,4]
[4,3,1]
Output
[1,2,4]
Example2
Input
3
2
[1,2,3]
[3,3]
Output
[1,0]
Notice
1≤n,q≤10000,1≤ai,x≤n
Input test data (one parameter per line)How to understand a testcase?
解法1:用线段树的区间更新,利用延迟更新标志add[],每次区间更新时间复杂度为O(logn)。
下面讲线段树的区间更新的2个链接不错:
https://blog.youkuaiyun.com/luomingjun12315/article/details/51124526
https://blog.youkuaiyun.com/thinking2013/article/details/41621657
注意:
1) 为什么add[]和sum[]的size定为4*n?
因为我们实际上想实现一个满二叉树,最下面一层为n的话,一共有1,2,4,8,...,n个节点。
如果n是2^k的话,那么1+2+4+8+...+n 一共有k+1项,其sum= 1*(1-2^(k+1))/(1-2)=2 * 2^k = 2*n。
但实际上n不一定是2^k,最极端情况下n=2^(k+1) - 1,那样下面还有1层,所以取4*n。
下面这个链接讲得蛮好
https://www.cnblogs.com/silentEAG/p/10808978.html
2) 注意线段树也可以用数组实现,类似heap。实际上线段树完全可以替代heap,因为线段树的max, min操作都可以是O(1)。
3) 当update和查询的时候,都需要pushdown。
4) 注意先排序。
class Solution {
public:
/**
* @param n: length of sequence
* @param q: Operating frequency
* @param a: the sequence
* @param b: the standard of each operation
* @return: How many numbers are subtracted by one after each operation
*/
void build(vector<int> &sum,vector<int> &a,int left,int right,int root)
{
if (left == right)
{
sum[root] = a[left - 1];
return;
}
int mid = (left + right) / 2;
build(sum, a, left, mid, 2 * root);
build(sum, a, mid + 1, right, 2 * root + 1);
}
void pushDown(vector<int> &sum,vector<int> &add, int root, int left_num, int right_num)
{
if (add[root]) //如果存在标记就向下更新
{
add[2 * root] += add[root]; //累加
add[2 * root + 1] += add[root]; //累加
sum[2 * root] += add[root] * left_num;
sum[2 * root + 1] += add[root] * right_num;
add[root] = 0;
}
}
void update(vector<int> &sum, vector<int> &add, int root_left, int root_right, int C, int query_left, int query_right, int root)
{
if (root_left <= query_left && query_right <= root_right)
{
sum[root] += C * (query_right - query_left + 1);
add[root] += C;
return;
}
int mid = (query_left + query_right) / 2;
pushDown(sum, add, root, mid - query_left + 1, query_right - mid); //m - l + 1 and r - m are counts needs to add the delay tag
if (root_left <= mid) update(sum, add, root_left, root_right, C, query_left, mid, 2 * root);
if (root_right > mid) update(sum, add, root_left, root_right, C, mid + 1, query_right, 2 * root + 1);
}
int query(vector<int> &sum,vector<int> &add, int root_left, int root_right, int query_left, int query_right, int root)
{
if (root_left <= query_left && query_right <= root_right)
{
return sum[root];
}
int mid = (query_left + query_right) / 2;
pushDown(sum, add, root, mid - query_left + 1, query_right - mid);
int ans = 0;
if (root_left <= mid) ans += query(sum, add, root_left, root_right, query_left, mid, 2 * root);
if (root_right > mid) ans += query(sum, add, root_left, root_right, mid + 1, query_right, 2 * root + 1);
return ans;
}
vector<int> sequenceMaintenance(int n, int q, vector<int> &a, vector<int> &b) {
sort(a.begin(), a.end());
vector<int> sum, add, ans;
sum.assign(4 * n, 0);
add.assign(4 * n, 0);
build(sum, a, 1, n, 1);
for (int i = 0; i < q; i++){
int left = 1, right = n, pos = -1;
while (left + 1 < right){
int mid = (left + right) / 2;
if (query(sum, add, mid, mid, 1, n, 1) >= b[i]){
right = mid;
pos = mid;
} else {
left = mid;
}
}
if (query(sum, add, right, right, 1, n, 1) >= b[i]){
pos = right;
}
if (query(sum, add, left, left, 1, n, 1) >= b[i]){
pos = left;
}
if (pos == -1) {
ans.push_back(0);
}
else {
ans.push_back( n - pos + 1);
update(sum, add, pos, n, -1, 1, n, 1); //C = -1, root = 1
}
}
return ans;
}
};
解法2:类似解法1,但用经典树状结构
解法3:类似解法1,但采用非递归线段树实现。
TBD
解法4:树状数组也可以实现O(logn)的区间修改。
下面2个链接不错。
https://blog.youkuaiyun.com/ljw_study_in_优快云/article/details/100101852?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase
https://www.cnblogs.com/dilthey/p/9366491.html
思路参考上面2个链接。主要是用C1[]和C2[]2个树状数组来实现。
class Solution {
public:
/**
* @param n: length of sequence
* @param q: Operating frequency
* @param a: the sequence
* @param b: the standard of each operation
* @return: How many numbers are subtracted by one after each operation
*/
vector<int> sequenceMaintenance(int n, int q, vector<int> &a, vector<int> &b) {
sort(a.begin(), a.end());
N = a.size(), M = b.size();
C1.resize(N + 1, 0);
C2.resize(N + 1, 0);
vector<int> ans;
//initialize C1 and C2
add(1, a[0]);
for (int i = 2; i <= N; ++i) {
add(i, a[i - 1] - a[i - 2]);
}
for (int i = 1; i <= M; ++i) {
int pos = binarySearch(1, N, b[i - 1]);
if (pos == -1) {
ans.push_back(0);
} else {
ans.push_back(N - pos + 1);
add_range(pos, N, -1);
// int range_sum = query_range(pos, N);
}
}
return ans;
}
private:
int N, M;
vector<long long> C1,C2; //分别记录 delta[i] 和 delta[i] * i
int lowbit(int x) {
return x & (-x);
}
void add(int pos, int val)
{
for(int i = pos; i <= N; i += lowbit(i)) {
C1[i] += val;
C2[i] += val * pos;
}
}
//区间[l,r]加上x
void add_range(int l, int r, int x) {
add(l, x);
add(r + 1,-x);
}
long long query(int pos) {
long long ret = 0;
for(int i = pos; i > 0; i -= lowbit(i)) {
ret += (pos + 1) * C1[i] - C2[i];
}
return ret;
}
//查询区间[l,r]的和
long long query_range(int l,int r) {
long long result = query(r) - query(l - 1);
return result;
}
int binarySearch(int start, int end, int target) {
while (start + 1 < end){
int mid = (start + end) / 2;
if (query_range(mid, mid) >= target){
end = mid;
} else {
start = mid;
}
}
if (query_range(start, start) >= target) return start;
if (query_range(end, end) >= target) return end;
return -1;
}
};