Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Алгоритмы C++

.pdf
Скачиваний:
683
Добавлен:
15.03.2016
Размер:
6 Mб
Скачать

break;

else if (rib_id [ g[v][j].v ] == -1) { rib_id [ g[v][j].v ] = g[v][j].id; q[t++] = g[v][j].v;

}

}

for (int v=cur.b, pv; v!=cur.a; v=pv) { int r = rib_id[v];

pv = v != ribs[r].a ? ribs[r].a : ribs[r].b; u[r][i] = n;

c[r][i] = ribs[i].c - ribs[r].c; c[i][r] = -c[r][i];

}

}

u[s][t] = n+1;

for (int i=0; i<n-1; ++i) u[s][i] = 1;

for (int i=n-1; i<m; ++i) u[i][t] = 1;

vector<int> pi (nn); pi[s] = INF;

for (int i=0; i<n-1; ++i) { pi[i] = INF;

for (int j=n-1; j<m; ++j) if (u[i][j])

pi[i] = min (pi[i], ribs[j].c-ribs[i].c); pi[s] = min (pi[s], pi[i]);

}

for (;;) {

vector<int> id (nn); deque<int> q; q.push_back (s); vector<int> d (nn, INF); d[s] = 0;

vector<int> p (nn, -1); while (!q.empty()) {

int v = q.front(); q.pop_front(); id[v] = 2;

for (int i=0; i<nn; ++i)

if (f[v][i] < u[v][i]) {

int new_d = d[v] + c[v][i] - pi[v] +

pi[i];

if (new_d < d[i]) { d[i] = new_d; if (id[i] == 0)

q.push_back (i); else if (id[i] == 2)

q.push_front (i); id[i] = 1;

p[i] = v;

}

}

}

for (int i=0; i<nn; ++i) pi[i] -= d[i];

for (int v=t; v!=s; v=p[v]) { int pv = p[v]; ++f[pv][v], --f[v][pv];

}

if (p[t] == s) break;

}

for (int i=0; i<m; ++i) pi[i] -= pi[s];

for (int i=0; i<n-1; ++i) if (f[s][i])

ribs[i].c += pi[i]; for (int i=n-1; i<m; ++i)

if (f[i][t])

ribs[i].c += pi[i];

... вывод графа ...

}

Покраска рёбер дерева

Это достаточно часто встречающаяся задача. Дано дерево G. Поступают запросы двух видов: первый вид - покрасить некоторое ребро, второй вид - запрос количества покрашенных рёбер между двумя вершинами.

Здесь будет описано достаточно простое решение (с использованием дерева отрезков), которое будет отвечать на запросы за O (log N), с препроцессингом (предварительной обработкой дерева) за O (M).

Решение

Для начала нам придётся реализовать LCA, чтобы каждый запрос второго вида (i,j) сводить к двум запросам (a,b), где a - предок b.

Теперь опишем препроцессинг собственно для нашей задачи. Запустим поиск в глубину из корня дерева, этот поиск

вглубину составит некоторый список посещения вершин (каждая вершина добавляется в список, когда поиск заходит

внеё, и каждый раз после того, как поиск в глубину возвращается из сына текущей вершины) - кстати говоря, этот

же список используется алгоритмом LCA. В этом списке будет присутствовать каждое ребро (в том смысле, что если i и j - концы ребра, то в списке обязательно найдётся место, где i и j идут подряд друг за другом), причём присутствовать ровно 2 раза: в прямом направлении (из i в j, где вершина i ближе к корню, чем вершина j) и в обратном (из j в i).

Построим два дерева отрезков (для суммы, с единичной модификацией) по этому списку: T1 и T2. Дерево T1 будет учитывать каждое ребро только в прямом направлении, а дерево T2 - наоборот, только в обратном.

Пусть поступил очередной запрос вида (i,j), где i - предок j, и требуется определить, сколько рёбер покрашено на пути между i и j. Найдём i и j в списке обхода в глубину (нам обязательно нужны позиции, где они встречаются впервые), пусть это некоторые позиции p и q (это можно сделать за O (1), если вычислить эти позиции заранее во время препроцессинга). Тогда ответом будет сумма T1[p..q-1] - сумма T2[p..q-1].

Почему? Рассмотрим отрезок [p;q] в списке обхода в глубину. Он содержит рёбра нужного нам пути из i в j, но также содержит и множество рёбер, которые лежат на других путях из i. Однако между нужными нам рёбрами

и остальными рёбрами есть одно большое отличие: нужные рёбра будут содержаться в этом списке только один раз, причём в прямом направлении, а все остальные рёбра будут встречаться дважды: и в прямом, и в

обратном направлении. Следовательно, разность T1[p..q-1] - T2[p..q-1] даст нам ответ (минус один нужно, потому что иначе мы захватим ещё лишнее ребро из вершины j куда-то вниз или вверх). Запрос суммы в дереве

отрезков выполняется за O (log N).

Ответ на запрос вида 1 (о покраске какого-либо ребра) ещё проще - нам просто нужно обновить T1 и T2, а

именно выполнить единичную модификацию того элемента, который соответствует нашему ребру (найти ребро в списке обхода, опять же, можно за O (1), если выполнить этот поиск в препроцессинге). Единичная модификация в дереве отрезков выполняется за O (log N).

Реализация

Здесь будет приведена полная реализация решения, включая LCA:

const int INF = 1000*1000*1000;

typedef vector < vector<int> > graph;

vector<int> dfs_list; vector<int> ribs_list; vector<int> h;

void dfs (int v, const graph & g, const graph & rib_ids, int cur_h = 1)

{

h[v] = cur_h; dfs_list.push_back (v);

for (size_t i=0; i<g[v].size(); ++i) if (h[g[v][i]] == -1)

{

ribs_list.push_back (rib_ids[v][i]); dfs (g[v][i], g, rib_ids, cur_h+1); ribs_list.push_back (rib_ids[v][i]); dfs_list.push_back (v);

}

}

vector<int> lca_tree; vector<int> first;

void lca_tree_build (int i, int l, int r)

{

if (l == r)

lca_tree[i] = dfs_list[l];

else

{

int m = (l + r) >> 1; lca_tree_build (i+i, l, m); lca_tree_build (i+i+1, m+1, r);

int lt = lca_tree[i+i], rt = lca_tree[i+i+1]; lca_tree[i] = h[lt] < h[rt] ? lt : rt;

}

}

void lca_prepare (int n)

{

lca_tree.assign (dfs_list.size() * 8, -1); lca_tree_build (1, 0, (int)dfs_list.size()-1);

first.assign (n, -1);

for (int i=0; i < (int)dfs_list.size(); ++i)

{

int v = dfs_list[i];

if (first[v] == -1) first[v] = i;

}

}

int lca_tree_query (int i, int tl, int tr, int l, int r)

{

if (tl == l && tr == r) return lca_tree[i];

int m = (tl + tr) >> 1; if (r <= m)

return lca_tree_query (i+i, tl, m, l, r); if (l > m)

return lca_tree_query (i+i+1, m+1, tr, l, r); int lt = lca_tree_query (i+i, tl, m, l, m);

int rt = lca_tree_query (i+i+1, m+1, tr, m+1, r); return h[lt] < h[rt] ? lt : rt;

}

int lca (int a, int b)

{

if (first[a] > first[b]) swap (a, b);

return lca_tree_query (1, 0, (int)dfs_list.size()-1, first[a], first[b]);

}

vector<int> first1, first2; vector<char> rib_used; vector<int> tree1, tree2;

void query_prepare (int n)

{

first1.resize (n-1, -1); first2.resize (n-1, -1);

for (int i = 0; i < (int) ribs_list.size(); ++i)

{

int j = ribs_list[i]; if (first1[j] == -1)

first1[j] = i;

else

first2[j] = i;

}

rib_used.resize (n-1);

tree1.resize (ribs_list.size() * 8); tree2.resize (ribs_list.size() * 8);

}

void sum_tree_update (vector<int> & tree, int i, int l, int r, int j, int delta)

{

tree[i] += delta; if (l < r)

{

int m = (l + r) >> 1; if (j <= m)

sum_tree_update (tree, i+i, l, m, j, delta);

else

sum_tree_update (tree, i+i+1, m+1, r, j, delta);

}

}

int sum_tree_query (const vector<int> & tree, int i, int tl, int tr, int l, int r)

{

if (l > r || tl > tr) return 0; if (tl == l && tr == r)

return tree[i]; int m = (tl + tr) >> 1; if (r <= m)

return sum_tree_query (tree, i+i, tl, m, l, r); if (l > m)

return sum_tree_query (tree, i+i+1, m+1, tr, l, r); return sum_tree_query (tree, i+i, tl, m, l, m)

+ sum_tree_query (tree, i+i+1, m+1, tr, m+1, r);

}

int query (int v1, int v2)

{

return sum_tree_query (tree1, 1, 0, (int)ribs_list.size()-1, first [v1], first[v2]-1)

- sum_tree_query (tree2, 1, 0, (int)ribs_list.size()-1, first[v1], first[v2]-1);

}

int main()

{

// чтение графа int n;

scanf ("%d", &n);

graph g (n), rib_ids (n); for (int i=0; i<n-1; ++i)

{

int v1, v2;

scanf ("%d%d", &v1, &v2); --v1, --v2;

g[v1].push_back (v2); g[v2].push_back (v1); rib_ids[v1].push_back (i); rib_ids[v2].push_back (i);

}

h.assign (n, -1); dfs (0, g, rib_ids); lca_prepare (n); query_prepare (n);

for (;;) {

if () {

//запрос о покраске ребра с номером x;

//если start=true, то ребро красится,

иначе покраска снимается

rib_used[x] = start;

sum_tree_update (tree1, 1, 0, (int)ribs_list.size()- 1, first1[x], start?1:-1);

sum_tree_update (tree2, 1, 0, (int)ribs_list.size()- 1, first2[x], start?1:-1);

}

else {

//запрос кол-ва покрашенных рёбер на пути между v1 и v2 int l = lca (v1, v2);

int result = query (l, v1) + query (l, v2);

//result - ответ на запрос

}

}

}

Задача 2-SAT

Задача 2-SAT (2-satisfiability) - это задача распределения значений булевым переменным таким образом, чтобы они удовлетворяли всем наложенным ограничениям.

Задачу 2-SAT можно представить в виде конъюнктивной нормальной формы, где в каждом выражении в скобках стоит ровно по две переменной; такая форма называется 2-CNF (2-conjunctive normal form). Например:

(a || c) && (a || !d) && (b || !d) && (b || !e) && (c || d)

Приложения

Алгоритм для решения 2-SAT может быть применим во всех задачах, где есть набор величин, каждая из которых может принимать 2 возможных значения, и есть связи между этими величинами:

Расположение текстовых меток на карте или диаграмме.

Имеется в виду нахождение такого расположения меток, при котором никакие две не пересекаются.

Стоит заметить, что в общем случае, когда каждая метка может занимать множество различных позиций, мы получаем задачу general satisfiability, которая является NP-полной. Однако, если ограничиться только двумя возможными позициями, то полученная задача будет задачей 2-SAT.

Расположение рёбер при рисовании графа.

Аналогично предыдущему пункту, если ограничиться только двумя возможными способами провести ребро, то мы придём к 2-SAT.

Составление расписания игр.

Имеется в виду такая система, когда каждая команда должна сыграть с каждой по одному разу, а требуется распределить игры по типу домашняя-выездная, с некоторыми наложенными ограничениями.

и т.д.

Алгоритм

Сначала приведём задачу к другой форме - так называемой импликативной форме. Заметим, что выражение вида a || b эквивалентно !a => b или !b => a. Это можно воспринимать следующим образом: если есть выражение a || b, и

нам необходимо добиться обращения его в true, то, если a=false, то необходимо b=true, и наоборот, если b=false, то необходимо a=true.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]