Q 查询的给定范围内 K 的可能余数计数
给定一个数组 arr[ ],其中包含 N 个正整数和一个整数 K 以及一个查询向量 Q 。可以询问两种类型的查询:
- 在类型 1 查询中,从索引 l 到 r 的所有元素都增加值 X。此查询的输入格式:1 lrx
- 在类型 2 查询中,对于给定的整数 K,如果从索引 l 到 r 的元素除以 k,则打印所有可能余数的计数。本次查询的输入格式:2 l r
例子:
Input: arr[ ] = {7, 13, 5, 9, 16, 21}, K=4, vector< vector< int > >Q = { {2, 3, 5}, { 1, 0, 4, 1}, {2, 2, 5} }
Output: 1 2 0 0
0 2 2 0
Explanation: The first query type is 2. so, for index 3 to 5, there is only one element => ( 16 ) whose remainder is 0 for given k=4. Two elements whose remainder is 1 => ( 9, 21 ). There is no element whose remainder is 2 or 3.
After second query arr will be: {8, 14, 6, 10, 17, 21} . because of all array elements are increased by 1 .
In the third query for index 2 to 5, there are two elements whose remainders are 1 (17, 21) and 2 (6, 10).There is no elements whose remainder is 0 or 3.
Input: arr[ ] = {6, 7, 8, 9, 10, 11}, k=5, vector< vector< int > >Q = { {2, 1, 1 } {2, 1, 3}, { 1, 1, 3, 2}, {2, 1, 3} }
Output: 0 0 1 0 0
0 0 1 1 1
1 1 0 0 1
天真的方法:一种简单的方法是在更新查询中迭代给定范围内的数组并更新值。对于另一种类型的查询,遍历给定范围并使用 k 取 mod,保留哈希数组以存储该余数的特定计数。但是这种方法的时间复杂度是 O(N*Q*k)
时间复杂度: O(N*Q*k)
辅助空间: O(k),用于维护大小为 k 的哈希数组
高效方法:上述方法可以使用分段树和惰性传播进行优化,基于以下思想:
直觉:
Here numbers of queries are high and there are queries also in which a range l to r has to be updated. Also we have to print answers for the given range l to r.
So, these are giving hints to use segment tree with lazy propagation, because in this method we can answer each query in O( log(N) ) time i.e. ( Height of segment tree). Along with this, we will use lazy propagation to update the range values in an efficient manner, because in lazy propagation, we only update the node of the segment tree which is currently required and will propagate the value to the child node, and will update this child node, whenever it is required.
To store this propagate value,
- Use an array named lazy (check code) Instead of a given value,
- Just update a range with val%k, because the main focus is remainder and this will not affect the answer.
- Use a 2D segment tree, so that each node of the segment tree contains an array of length K, to store the count of all possible remainders.
插图:
For arr[]={1, 2, 5}, k=4, segment tree looks like:
so basically node segment[ ind ][ j ] will contain the count of elements whose remainder is j corresponding to a range covered by the indth index of the segment tree.
In other words, if the indth index of segment tree cover range a to b of the array then segment[ ind ][ 0 ] represents, the count of elements whose remainder is 0 in range a to b.
Similarly segment[ ind ][ j ] will represent the count of elements whose remainder is j for given K in range a to b.
Here the main trick is if any range is updated by value x, then all elements of the array of length K which is attached with the segment tree’s node, will do the right cyclic shift by x%k positions.
Suppose if any range values are increased by 1, it means count of elements whose remainder was 0 now it become count of elements whose remainder is 1.
Before modifying that node, store the value in the temporary array.
After that, modify the node of the segment tree by segment[ ind ][ (val+i)%k ]=temp[ i ]
下面是上述方法的实现:
C++
// C++ program for Find count of all
// possible remainders for given integer K
// in the given ranges for Q queries.
#include
using namespace std;
#define MXX 100001
// to store propagate value
int lazy[4 * MXX];
int segment[4 * MXX][51];
int ans[51];
// build segment tree for given arr
void build(int arr[], int low, int high, int ind, int k)
{
if (low == high) {
int rem = arr[low] % k;
// mark 1 at ind corresponding to remainder
segment[ind][rem] = 1;
return;
}
int mid = (low + high) >> 1;
build(arr, low, mid, 2 * ind + 1, k);
build(arr, mid + 1, high, 2 * ind + 2, k);
// befor returning, compute answer for
// all possible remainders for node ind
for (int i = 0; i < k; i++) {
segment[ind][i] = segment[2 * ind + 1][i]
+ segment[2 * ind + 2][i];
}
}
// to update a range l to r
void update(int l, int r, int low, int high, int ind, int k,
int val)
{
lazy[ind] %= k;
// if any value is panding than update it
if (lazy[ind] != 0) {
if (low != high) {
// propagate panding value to its children
lazy[2 * ind + 1] += lazy[ind];
lazy[2 * ind + 2] += lazy[ind];
lazy[2 * ind + 1] %= k;
lazy[2 * ind + 2] %= k;
}
int incr = lazy[ind];
// make temporary vector to store value
// so we can perform cycle operation without
// loosing actual values
vector temp(k);
for (int i = 0; i < k; i++) {
temp[i] = segment[ind][i];
}
// do cyclic shift operation
for (int i = 0; i < k; i++) {
segment[ind][(incr + i) % k] = temp[i];
}
// after done panding update mark it 0.
lazy[ind] = 0;
}
// invalid range then return
if (high < low || low > r || high < l)
return;
// the currant range is subset of
// our actual range so update value
if (low >= l && high <= r) {
val %= k;
vector temp(k);
for (int i = 0; i < k; i++) {
temp[i] = segment[ind][i];
}
for (int i = 0; i < k; i++) {
segment[ind][(val + i) % k] = temp[i];
}
if (low != high) {
lazy[2 * ind + 1] += val;
lazy[2 * ind + 2] += val;
lazy[2 * ind + 1] %= k;
lazy[2 * ind + 2] %= k;
}
return;
}
int mid = (low + high) >> 1;
// go to left and right side
update(l, r, low, mid, 2 * ind + 1, k, val);
update(l, r, mid + 1, high, 2 * ind + 2, k, val);
// after updating and before returning,
// calculate answer
for (int i = 0; i < k; i++) {
segment[ind][i] = segment[2 * ind + 1][i]
+ segment[2 * ind + 2][i];
}
}
// to compute answer of a query
// most of operation are same as update function
void query(int l, int r, int low, int high, int ind, int k)
{
lazy[ind] %= k;
if (lazy[ind] != 0) {
if (low != high) {
lazy[2 * ind + 1] += lazy[ind];
lazy[2 * ind + 2] += lazy[ind];
lazy[2 * ind + 1] %= k;
lazy[2 * ind + 2] %= k;
}
int incr = lazy[ind];
vector temp(k);
for (int i = 0; i < k; i++) {
temp[i] = segment[ind][i];
}
for (int i = 0; i < k; i++) {
segment[ind][(incr + i) % k] = temp[i];
}
lazy[ind] = 0;
}
if (high < low || low > r || high < l)
return;
// this range is subset of our actual
// require range so compute answer for
// this range
if (low >= l && high <= r) {
for (int i = 0; i < k; i++)
ans[i] += segment[ind][i];
return;
}
int mid = (low + high) >> 1;
query(l, r, low, mid, 2 * ind + 1, k);
query(l, r, mid + 1, high, 2 * ind + 2, k);
}
// after printing answer
// reset ans array
void print(int k)
{
for (int i = 0; i < k; i++)
cout << ans[i] << " ";
cout << "\n";
}
void reset()
{
for (int i = 0; i < 51; i++)
ans[i] = 0;
}
int main()
{
int arr[] = { 7, 13, 5, 9, 16, 21 };
int n = sizeof(arr) / sizeof(arr[0]);
int q = 3, k = 4;
// build segment tree
build(arr, 0, n - 1, 0, k);
// first query
int x, l = 3, r = 5;
query(l, r, 0, n - 1, 0, k);
print(k);
reset();
// second query
l = 0, r = 4, x = 1;
update(l, r, 0, n - 1, 0, k, x);
// third query
l = 2, r = 5;
query(l, r, 0, n - 1, 0, k);
print(k);
reset();
return 0;
}
1 2 0 0
0 2 2 0
时间复杂度:O(Q*K*logN)
辅助空间:O(N*K)