Skip to content

Commit

Permalink
simplified implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
sslotin committed Sep 7, 2019
1 parent 20a942e commit 040eaa3
Showing 1 changed file with 40 additions and 58 deletions.
98 changes: 40 additions & 58 deletions sqrt.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,11 +181,11 @@ for (int i = 0; i < c; i++) {
while (r < q.r)
s += a[++r];
while (l < q.l)
            s -= a[l++];
        while (l > q.l)
            s += a[--l];
        ans[q.idx] = s;
    }
s -= a[l++];
while (l > q.l)
s += a[--l];
ans[q.idx] = s;
}
}
```

Expand Down Expand Up @@ -229,52 +229,44 @@ for (int i = 0; i < c; i++) {

3. Все вершины тяжелые. Аналогично — тип третьей вершины в разборе предыдущего случая нигде не использовался; важно лишь то, что тяжелых вершин $b$ немного.

Само решение максимально простое: отсортируем вершины графа по их степени, ориентируем ребра $v \rightarrow u, v \le u$. Теперь внутренним циклом будем считать пути $v \rightarrow u \rightarrow w, v \le u \le w$, а потом проверять существование ребра $v \rightarrow w$.
Само решение максимально простое: отсортируем вершины графа по их степени, ориентируем ребра $v \rightarrow u, v \le u$; теперь внутренним циклом будем перебирать пути $v \rightarrow u \rightarrow w, v \le u \le w$, а потом проверять существование ребра $v \rightarrow w$.

```c++
int vert[maxn], deg[maxn];
vector<int> g[maxn];
vector<int> g[maxn], p(n); // исходный граф и список номеров вершин
iota(p.begin(), p.end(), 0); // 0, 1, 2, 3, ...

// чтобы не копипастить сравнение:
auto cmp = [&](int a, int b) {
return g[a].size() < g[b].size() || (g[a].size() == g[b].size() && a < b);
};

// в таком порядке мы будем перебирать вершины
sort(p.begin(), p.end(), cmp);

// теперь удалим все лишние рёбра (ведущие в более тяжелые вершины)
for (int v = 0; v < n; v++) {
vector<int> &t = g[v];
// отсортируем их и удалим какой-то суффикс
sort(t.begin(), t.end(), cmp);
while (t.size() > 0 && cmp(t[t.size()-1], v))
t.pop_back();
reverse(t.begin(), t.end());
}

ll solve() {
for (int i = 0; i < n; i++) {
vert[i] = i;
}
for (int i = 0; i < m; i++) {
int a, b;
cin >> a >> b;
deg[a]++;
deg[b]++;
g[a].push_back(b);
g[b].push_back(a);
}
sort(vert, vert + n, [&](int a, int b) {
return make_pair(deg[a], a) < make_pair(deg[b], b);
});
for (int i = 0; i < n; i++) {
sort(g[i].rbegin(), g[i].rend(), [&](int a, int b) {
return make_pair(deg[a], a) < make_pair(deg[b], b);
});
while (g[i].size() && make_pair(deg[g[i].back()], g[i].back()) < make_pair(deg[i], i)) {
g[i].pop_back();
}
reverse(g[i].begin(), g[i].end());
}
ll ans = 0;
for (int i = 0; i < n; i++) {
int cur = vert[i];
for (auto v : g[cur]) {
cnt[v]++;
}
for (auto v : g[cur]) {
for (auto it : g[v]) {
ans += cnt[it];
}
}
for (auto v : g[cur]) {
cnt[v]--;
}
}
return ans;
// рядом с каждой вершиной будем хранить количество
// ранее просмотренных входящих рёбер (v -> w)
vector<int> cnt(n, 0);
int ans = 0;

for (int v : p) {
for (int w : g[v])
cnt[w]++;
for (int u : g[v])
for (int w : g[u])
ans += cnt[w]; // если в графе нет петель, то cnt[w] это 0 или 1
// при переходе к следующему v массив нужно занулить обратно
for (int w : g[v])
cnt[w]--;
}
```
Expand All @@ -286,20 +278,10 @@ ll solve() {
Теперь у нас не чётко разделённые блоки, а плавающие корзины разного размера, в которых просто хранятся маленькие массивы с элементами, а также сумма на всей корзине. В самом начале добавим в них примерно по корню последовательных элементов, а при запросе обновления найдём две нужные корзины и полностью перестроим — за их суммарный размер. Чтобы корзины не стали слишком большими, просто каждый корень запросов будем их полностью перестраивать.
```c++
// TODO: законтрибьюте кто-нибудь реализацию
```

## Подбор константы
На скорость работы очень сильно влияет размер блока. Мы для простоты использовали одну и ту же константу и для количества блоков, и для их размера, но на практике их часто нужно подбирать.
![](https://pp.userapi.com/c851420/v851420187/140d89/zp7lDsoCFCM.jpg)

Иногда асимптотики «тяжелой» и «лёгкой» части получаются разными, потому что мы где-то не смогли обойтись без какой-нибудь структуры, которая внесла лишний логарифм.
Чаще всего, в качестве оптимального размера блока $c^*$ можно взять что-то близкое к решению уравнения $g \cdot \frac{n}{c} = f \cdot c$, где $f$ и $g$ это асимптотики блочной и не-блочной частей соответственно. Впрочем, надо также учитывать, что походы в какое-нибудь декартово дерево совсем не в логарифм раз медленнее линейного прохода по массиву.




0 comments on commit 040eaa3

Please sign in to comment.