SQRT heuristics
SQRT heuristics
Root heuristics is a generalized name for methods and data structures that rely on the fact:
if you split a set of elements into blocks of elements, then there will be no more than blocks.
SQRT heuristics
Root heuristics is a generalized name for methods and data structures that rely on the fact:
if you split a set of elements into blocks of elements, then there will be no more than blocks.
Key idea:
“there are many small objects, but they are small; there are few large objects”
A classic example is square-root factorization.
If is a “large” divisor of the number , then it corresponds to a “small” divisor:
That is, there are few large divisors, because each large one corresponds to a small one.
Such logic is often transferred to other objects: strings, graph vertices, query types, item weights, etc.
You need to maintain a set of strings online and process operations:
Split strings into:
Here is chosen as , where is the total length of all strings.
Then there are few long strings: no more than .
Given a graph with vertices and edges. You need to count the number of cycles of length 3 (triangles).
A vertex is heavy if its degree is greater than , otherwise it is light.
There are few heavy vertices: no more than .
We break it down by the number of heavy vertices in the triangle:
No heavy vertices
The triangle has an edge , the third vertex lies in the intersection/union of neighbors.
Since degrees are small, we get a bound on the order of .
One heavy
Similarly: there is a “light” edge → the estimate is also .
Two heavy
Fix the edge , there are ways.
There are only heavy vertices → again .
Three heavy
There are few heavy vertices, brute force also fits.
In total, there are “not that many” triangles: on the order of .
Sort vertices by (degree, id).
Then iterate over paths of the form in the correct order and check for the presence of the edge .
There are items with weights , and:
Standard DP works in .
If there are distinct positive weights with sum , then:
hence:
That is, the number of distinct weights is at most .
If weight occurs times, decompose into a sum of powers of two and replace these items with items of weights:
This preserves all sums of the form for .
After such a transformation, the number of items becomes on the order of , and then we run the standard DP.
Note: the knapsack solution can be greatly sped up using bitset.
long long count_triangles(int n, vector<vector<int>> &g) {
vector<int> deg(n);
for (int v = 0; v < n; v++) deg[v] = (int)g[v].size();
auto better = [&](int a, int b) {
if (deg[a] != deg[b]) return deg[a] < deg[b];
return a < b;
};
// sort vertices by (degree, id)
vector<int> order(n);
iota(order.begin(), order.end(), 0);
sort(order.begin(), order.end(), better);
// sort adjacency lists in the same order
for (int v = 0; v < n; v++) {
sort(g[v].begin(), g[v].end(), better);
}
vector<int> cnt(n, 0);
long long ans = 0;
for (int v : order) {
// mark all "forward" neighbors of v
for (int u : g[v]) {
if (!better(v, u)) break;
cnt[u] = 1;
}
// count triangles v - u - w where v<u<w in this order
for (int u : g[v]) {
if (!better(v, u)) break;
for (int w : g[u]) {
if (!better(u, w)) break;
ans += cnt[w];
}
}
// unmark
for (int u : g[v]) {
if (!better(v, u)) break;
cnt[u] = 0;
}
}
return ans;
}