Триангуляция многоугольника минимальной мощности
Дан многоугольник
Необходимо провести в нём некоторое количество отрезков, чтобы многоугольник распался на минимальное количество треугольников
Очевидно, что этими отрезками будут внутренние диагонали многоугольника.
Допустим, нам нужно в решении провести диагональ (i, j), тогда многоугольник распадается на два подмногоугольника и задачу можно теперь решать для них отдельно. Получившиеся количества треугольник складываем. За основу берём то, что если многоугольник составлен из отрезков, полностью лежащих внутри исходного многоульника, и в нём три вершины, то минимальная мощность его триангуляции равна 1. Таким образом мы пришли к рекурентному соотношению. Можно написать перебор. Но многие подмногоугольники будут рассматриваться по несколько раз, и их триангуляции от этого меняться не будут. Поэтому мы будем запоминать минимальные триангуляции подмногоугольников. Удобнее всего представлять подмногоугольник как последовательность битов, где каждый бит обозначает присутствует ли соответствующая вершина исходного многоугольника в подмногоугольнике.
Теперь все подмногоугольники будут рассматриваться по одному разу. Всего их около 2n, где n - количество вершин. Для каждого подмногоугольника мы будем за O(n2) искать внутреннюю диагональ. Сами внутренние диагонали рационально запомнить препроцессингом за O(n3). В итоге получается асимптотика O(2n*n2 + n3) = O(2n*n2).
Асимптотика O(2n*n2).
Листинг C++
#define all(v) (v).size (), (v).end ()
#define sz size ()
#define pb push_back
#define forn(i, n) for ((i) = 0; (i) < (n); ++ (i))
#define forab(i, a, b) for ((i) = (a); (i) <= (b); ++ (i))
#define Forn(i, n) for (int (i) = 0; (i) < (n); ++ (i))
#define Forab(i, a, b) for (int (i) = (a); (i) <= (b); ++ (i))
#define forv(it, v) for (it = (v).begin (); it != (v).end (); ++ it)
const double pi = 3.14159265358979323846264338327;
const double eps = 1e-7;
const double inf = 1e9;
#define eq(a, b) (abs ((a) - (b)) <= eps)
#define ls(a, b) (! eq ((a), (b)) && (a) < (b))
#define gr(a, b) (! eq ((a), (b)) && (a) > (b))
#define sqr(a) ((a) * (a))
double SAT (point a, point b, point c) // ориентированная площадь треугольника - лежит ли вершина b справа от вектора a->c
{
return 0.5 * (a.x * b.y + b.x * c.y + c.x * a.y - a.y * b.x - b.y * c.x - c.y * a.x);
}
int sSAT (point a, point b, point c) // знак ориентированной площади треугольника
{
double s = SAT (a, b, c);
if (gr (s, 0)) return 1;
if (ls (s, 0)) return - 1;
return 0;
}
bool is_convex (vector < point > & v, int a) // выпуклый ли подмногоугольник
{
int i, j, k;
int n = v.size ();
vector < point > p;
forn (i, n)
if (((a >> i) & 1) && (p.sz < 3 || sSAT (p[p.sz - 1], p[p.sz - 2], v[i]) != 0))
p.pb (v[i]);
int m = p.sz;
if (m < 3) return true;
int s = sSAT (p[0], p[1], p[2]);
forn (i, m)
{
j = (i + 1) % m;
k = (j + 1) % m;
if (sSAT (p[i], p[j], p[k]) != s) return false;
}
return true;
}
bool is_crossing (point a, point b, point c, point d) // пересечение двух отрезков, не лежащих на одной прямой
{
return sSAT (a, c, b) != sSAT (a, d, b) && sSAT (c, a, d) != sSAT (c, b, d);
}
int paint0 (int a, int n, int i, int j) // покрытие двоичного числа нулями в позициях [i; j]
{
int a1 = a & ((1 << i) - 1);
int a2 = a & (((1 << (n - j)) - 1) << (j + 1));
return a1 | a2;
}
int draw0 (int a, int n, int i, int j) // покрытие двоичного числа нулями во всех позициях которые не принадлежат [i; j]
{
int a1 = a & ((1 << i) - 1);
int a2 = a & (((1 << (n - j)) - 1) << (j + 1));
return a & (~ (a1 | a2));
}
int cnt_ones (int a, int n) // количество единиц в двоичном представлении числа
{
int cnt = 0;
Forn (i, n)
cnt += ((a >> i) & 1);
return cnt;
}
int normal (int p, vector < point > & v) // удаление из подмногоугольника вершин, которые лежат на одной прямой с двумя соседними
{
int i, j, k;
int n = v.size ();
forn (i, n)
if ((p >> i) & 1)
{
j = (i - 1 + n) % n;
while (((p >> j) & 1) == 0)
j = (j - 1 + n) % n;
k = (i + 1) % n;
while (((p >> k) & 1) == 0)
k = (k + 1) % n;
if (sSAT (v[j], v[i], v[k]) != 0)
break;
}
if (i >= n) return 0;
int t = 0;
int i0 = i;
j = - 1;
do
{
k = (i + 1) % n;
while (k != j && ((p >> k) & 1) == 0)
k = (k + 1) % n;
if (j == - 1 || sSAT (v[j], v[i], v[k]) != 0)
t |= 1 << i;
j = i;
i = k;
}
while (i != i0);
return t;
}
int triangulate_mincnt (vector < point > & v, vector < int > & T1, vector < int > & T2)
{
int i, j;
int n = v.size ();
if (is_convex (v, (1 << n) - 1))
{
forn (i, n - 3)
{
T1.pb (0);
T2.pb (i + 2);
}
return n - 2;
}
char ** D = new char * [n];
char ** D2 = new char * [n];
forn (i, n)
{
D[i] = new char [n];
D2[i] = new char [n];
forn (j, n)
{
D[i][j] = is_diagonal (v, i, j);
D2[i][j] = point_in_polygon (part_segment (v[i], v[j], 1, 1), v);
}
}
int P = 1 << n;
int * M = new int [P];
int * prev1 = new int [P];
int * prev2 = new int [P];
forn (i, P)
M[i] = inf, prev1[i] = - 1, prev2[i] = - 1;
int * q = new int [n];
forn (i, n)
q[i] = 0;
Forab (p, 7, (1 << n) - 1)
{
if (cnt_ones (p, n) < 3) continue; // если точек меньше трёх - это не многоугольник
forn (i, n) // проверяем, все ли рёбра многоугольника являются внутренними диагоналями
if ((p >> i) & 1) // или сторонами исходного многоугольника
{
j = (i + 1) % n;
while (j != i && ((p >> j) & 1) == 0)
j = (j + 1) % n;
if (j != i && D2[i][j] == 0)
break;
}
if (i < n) continue;
int t = normal (p, v); // сокращаем многоугольник, засчёт удаления точек, лежащих на одной прямой
if (t != p) // если многоугольник сократился, значит мы его уже рассматривали
{
M[p] = M[t];
prev1[p] = prev1[t];
prev2[p] = prev2[t];
continue;
}
if (is_convex (v, p)) // если многоугольник выпуклый - проводим любую диагональ
{
forn (i, n)
if ((p >> i) & 1)
break;
prev1[p] = i;
forab (j, i + 1, n - 1)
if ((p >> j) & 1)
break;
forab (i, j + 1, n - 1)
if ((p >> i) & 1)
break;
prev2[p] = i;
M[p] = cnt_ones (p, n) - 2;
continue;
}
int m = 0;
forn (i, n)
if ((p >> i) & 1)
q[m ++] = i;
forab (i, 0, m - 2) // перебираем все диагонали и проводим их, оценивая
forab (j, i + 1, m - 1) // мощности триангуляций полученных частей
if (D[q[i]][q[j]])
{
int p1 = paint0 (p, n, q[i] + 1, q[j] - 1);
int p2 = draw0 (p, n, q[i], q[j]);
if (M[p] > M[p1] + M[p2])
{
M[p] = M[p1] + M[p2];
prev1[p] = q[i];
prev2[p] = q[j];
}
}
}
vector < int > st;
st.push_back ((1 << n) - 1);
while (st.size ()) // находим, какие мы провели диагонали с помощью поиска в глубину
{
int p = st.back ();
st.pop_back ();
if (M[p] == 1 || M[p] < 1 || M[p] > n) continue;
T1.push_back (prev1[p]);
T2.push_back (prev2[p]);
int p1 = paint0 (p, n, prev1[p] + 1, prev2[p] - 1);
int p2 = draw0 (p, n, prev1[p], prev2[p]);
st.push_back (p1);
st.push_back (p2);
}
return M[(1 << n) - 1];
}
13:28
04.08.2009
По всем вопросам обращаться: rumterg@gmail.com