BZOJ 3343 and Chunks
BZOJ 3343 and Chunks
Problem Statement
There are N≤1000000 counters. You are asked to do Q≤3000 actions on these counters, either:
- Increment all counters between L and R by W, given that 1≤L≤R≤N and 1≤W≤1000. This command will be in the format "M <L> <R> <W>", where L, R, and W are integers.
- Count the amount of counters between L and R and are greater than W, given that 1≤L≤R≤N and 1≤W≤1000. This command will be in the format "A <L> <R> <W>", where L, R, and W are integers.
Core Idea
Any array of integer size N can be broke up into √N+1 chunks of √N elements each for the first N blocks and then the remainder amount for the last block.
Although this may seem as the bloody obvious at first, splitting up an extremely large array into chunks and then doing work on the chunks can make a program vastly more efficient. There are various example:
- Searching - if a block's lowest value is above the target or a block's highest value is lower than the target, skip it
- Counting - a sorted block can be binary searched to find in O(log √N) how many satisfy, making a total of O(√N log √N) vs O(N log N log N) for simple binary search
Application
In this problem, we have two things to deal with - updating (increment) and querying (counting).
Notice the huge N (1 million!) and the huge Q (3 thousand!), so in worst case we would have to do about 3 billion instructions for every query. Even O(N) wouldn't work.
We have a few choices:
- Binary search. However, this is not even close to applicable - we aren't searching, we are counting, and how would we deal with increment?
- Fenwick tree (prefix sums). This is a good start - but if O(N) wouldn't work, adding a near constant factor of 5 (the value of the inverse Ackermann function) would not be a feasible algorithm.
- Chunky counting. Similar to prefix sums, this method is very efficient and is the best.
But how? We will need to deal with it in two different ways:
Increment
Say we want to increment all values between X and Y by 7. We already have a chunky array. So, how do we do this?
Some values between X and Y do not fit entirely into a chunk. For example, we can have the following, where N is 36, each chunk is size 6, X is 10 and Y is 20:
01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|
X Y
There really is no other method to deal with those, so let's simply manually add them by 7. Although this method seems to be slow, it really is only O(√N) at worst case.
Now, to deal with the chunks. Clearly we cannot add all of the elements in the chunks together, one by one. Instead, let's use a "bonus" - for the entire chunk, each element's real value is that element's value plus the chunk bonus. So, now we simply increment the chunk bonus by 7!
Counting
To count the number of elements between X and Y that is greater than V, we return to the chunks.
01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|
X Y
Again, some pieces such as 10 through 12 and 19 through 20 in the above example can't fit, so we count them manually, taking into account the bonuses as well.
Then, since we initially sorted all the chunks (refer to code's comments), we can do a binary search on each chunk, instead of manually iterating! For more detail, refer to the code's comments.
Source Code
#include<iostream> #include<cstdio> #include<algorithm> #include<cmath> using namespace std; int n; // n: total heights int q; // q: total commands int m; // m: total chunks int chunk; // chunk: length of a chunk int a[1000001]; // a: heights int b[1000001]; // b: chunk-heights, chunk-sorted version of a, sorted by reset() int pos[1000001]; // pos: the chunk I is in int add[1000001]; // add: the bonus for every element in chunk I void reset(int x) // sort chunk X { int l=(x-1)*chunk+1,r=min(x*chunk,n); // calculate boundaries of chunk X; L is left, R is right for(int i=l;i<=r;i++) // for each height in chunk X (between L and R) b[i]=a[i]; // assign the chunk-height to the height sort(b+l,b+r+1); // sort the chunk-heights in the range of this chunk } int find(int x,int v) // search for amount of chunk-heights in chunk V greater than X { int l=(x-1)*chunk+1,r=min(x*chunk,n); // this is a binary search in chunk V int last=r; // don't do this, use STL instead while(l<=r) { int mid=(l+r)>>1; if(b[mid]<v)l=mid+1; else r=mid-1; } return last-l+1; } void update(int x,int y,int v) // increase the height of the interval [x, y] by v { if(pos[x]==pos[y]) // if x and y are in the same chunk: { for(int i=x;i<=y;i++)a[i]=a[i]+v; // increment all in the interval by V } else // otherwise: { for(int i=x;i<=pos[x]*chunk;i++)a[i]=a[i]+v; // increment all from X to the start of the next chunk by V for(int i=(pos[y]-1)*chunk+1;i<=y;i++)a[i]=a[i]+v; // increment all from the start of the chunk with Y to Y by V } reset(pos[x]);reset(pos[y]); // sort the chunks of X and Y after being incremented for(int i=pos[x]+1;i<pos[y];i++) // for each chunk between X and Y exclusive: add[i]+=v; // increment the bonus for that chunk by V } int query(int x,int y,int v) // query the amount of heights in the interval [x, y] are taller than v { int sum=0; // set the answer to 0 if(pos[x]==pos[y]) // if x and y are in the same chunk: { for(int i=x;i<=y;i++) // for each height in [x, y] if(a[i]+add[pos[i]]>=v) sum++; // if its height plus bonus for the chunk is greater than v, increment answer } else // otherwise: { for(int i=x;i<=pos[x]*chunk;i++) // for all heights from X to the start of the next chunk if(a[i]+add[pos[i]]>=v)sum++; // if its height plus bonus for the chunk is greater than v, increment answer for(int i=(pos[y]-1)*chunk+1;i<=y;i++) // for all heights from the start of chunk Y to Y if(a[i]+add[pos[i]]>=v)sum++; // if its height plus bonus for the chunk is greater than v, increment answer } for(int i=pos[x]+1;i<pos[y];i++) // for each chunk between X and Y exclusive: sum+=find(i,v-add[i]); // increment by how many in chunk are greater than v return sum; // return answer } int main() { freopen("bzoj3343.in","r",stdin); // redirect input freopen("bzoj3343.out","w",stdout); // redirect output scanf("%d%d",&n,&q); // read heights and queries chunk=int(sqrt(n)); // set chunk length to the square root of N rounded down for(int i=1;i<=n;i++) // read in the heights: { scanf("%d",&a[i]); // scan input pos[i]=(i-1)/chunk+1; // register its position b[i]=a[i]; // register its chunk-height } if(n%chunk)m=n/chunk+1; // if it isn't a perfect square, make room for another chunk else m=n/chunk; // if not, it's the same for(int i=1;i<=m;i++)reset(i); // update and sort chunk-heights for(int i=1;i<=q;i++) // read in commands { char ch[5];int x,y,v; // initialize command and arguments scanf("%s%d%d%d",ch,&x,&y,&v); // read command and arguments if(ch[0]=='M')update(x,y,v); // if it's an increase height, run update else printf("%d\n",query(x,y,v)); // if it's an query, run query and print out result } return 0; }
Comments
Post a Comment