Wednesday, July 6, 2016

Codeforces Codeforces Round #359 (Div. 1) - D. Kay and Eternity

Problem Statement:
http://codeforces.com/contest/685/problem/D

Summary:
In an infinite 2D array, there are n < 10^5 cells (x[i], y[i]) with value 1, where -1e9 <= x[i], y[i] <= 1e9. The rest of the cells are 0. For each m = 1 to n, compute the number of squares with dimension k x k (k <= 300), with the property that each of those squares has exactly m non-zero cells in it.



Solution:
Let's uniquely define a square as the index (i, j) of its left bottom corner. Imagine that we have a 2D array S such that S[i][j] is the number of non-empty cell in the square (i, j). One way to compute S is as follows: for each non-empty cell (x, y), add 1 to every S[s][t] such that s is in [x-k+1, x] and t is in [y-k+1, y].

Now the strategy is to make the above operation fast enough. Let's suppose that we have another array freq[s][t] which is initially 0 for all entries. Instead of adding 1 to every S[s][t] in those ranges, we can add 1 to the row x-k+1 in freq for column in range [y-k+1, y], and subtract 1 from the row x+1 in F from column [y-k+1, y]. S is then can be viewed as the cumulative frequency of freq along the column, i.e. S[s][t] = freq[s][t] + freq[s-1][t] + freq[s-2][t] + ... . Hence using this technique, we can transform all (x, y) cells into two queries of the form (row, L, R, val): (x-k+1, y-k+1, y, +1) and (x+1, y-k+1, y, -1) where (row, L, R, val) means perform addition of val to entries in freq[row][L ... R].

With the concept of cumulative frequency and transformation of the problem space into update queries, we can solve the problem as follows:
1) We will perform a sweeping line over all queries (row, L, R, val) in increasing row.
2) Firstly initialise two arrays last_updated[] and cnt[], where last_updated[i] stores the row of the latest update query performed on column i, while cnt[i] stores the value freq[last_updated[i]][i], the most current frequency value of column i. Also initialise ans[i] which will contain the total number of squares containing i non-empty cells.
3) For each query (row, L, R, val):
Iterate through i = L to R, update ans[cnt[i]] by adding row - last_updated[i] (because all values in S[row-1] to S[last_updated[i]] are equal to cnt[i]). Then update last_updated[i] = row, and update cnt[i] = cnt[i] + val.

Since we need to sort the queries in increasing row order, and for each query we need to iterate through k values, overall we need O(n log n + nk) time. Furthermore we need O(nk) space to store the corresponding cnt[] and last_updated[] of all possible column ranges that contains at least a valid square.
Below is the implementation of the above solution. (Note that the shrink_to_fit call is needed to pass the memory limit of this problem...)

Implementation:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <utility>
using namespace std;
constexpr int N = 100001;
constexpr int K = 301;
struct Query {
  int row;
  int left;
  int right;
  bool val;
  bool operator<(const Query& other) const {
    if (row == other.row) {
      return left == other.left ? right < other.right :
                                  left < other.left;
    }
    return row < other.row;
  }
};
constexpr int MIN_INF = (int) -1e9 - 7;
int n, k;
vector<int> last_updated;
vector<int> cnt;
vector<int> arr;
vector<int64_t> ans;
vector<Query> queries;
int getIndex(int val) {
  int lo=0, hi=arr.size()-1, mid;
  while(lo<=hi){
    mid = (lo+hi)/2;
    if (arr[mid] < val) {
      lo = mid+1;
    } else {
      hi = mid-1;
    }
  }
  return lo;
}
int main(){
  int x,y;
  scanf("%d%d",&n,&k);
  vector<pair<int,int>> points;
  for (int i=0;i<n;++i){
    scanf("%d%d",&x,&y);
    points.push_back({x,y});
  }
  for (int i=0;i<n;++i){
    for (int j=0;j<k;++j){
      arr.push_back(points[i].second-k+j+1);
    }
  }
  points.clear();
  sort(arr.begin(), arr.end());
  int j=0;
  for (int i=1;i<arr.size();++i){
    if (arr[j] == arr[i]) continue;
    arr[++j] = arr[i];
  }
  arr.resize(j+1);
  for (int i=0;i<n;++i){
    int left = getIndex(points[i].second-k+1);
    int right = getIndex(points[i].second);
    queries.push_back({points[i].first-k+1, left, right, true});
    queries.push_back({points[i].first+1, left, right, false});
  }
  sort(queries.begin(), queries.end());
  arr.clear();
  arr.shrink_to_fit();
  
  ans.resize(n+1, 0);
  cnt.resize(j+1, 0);
  last_updated.resize(j+1, MIN_INF);
  for (int q=0;q<2*n;++q) {
    for (int i=queries[q].left; i <= queries[q].right; ++i){
      ans[cnt[i]] += queries[q].row - last_updated[i];
      last_updated[i] = queries[q].row;
      cnt[i] += queries[q].val ? 1 : -1;
    }
  }
  for(int i=1;i<=n;++i){
    cout << ans[i] << " ";
  }
  cout << endl;
  return 0;
}