Оптимизации динамического программирования: различия между версиями

Материал из Олимпиадное программирование в УлГТУ
Перейти к навигации Перейти к поиску
Нет описания правки
 
(не показано 10 промежуточных версий этого же участника)
Строка 1: Строка 1:
== Оптимизация Кнута (массив, число; N^2 * P → N^2) ==
Решается задача «Разделить массив на partCount частей, минимизировав нечто». Паттерн — двумерная динамика, позиция в массиве + число.
Базовое решение за O(N^2 * P):
vector<vector<long long>> res(a.size() + 1, vector<long long>(partCount + 1, 1e18));
res[0][0] = 0;
for (int size = 1; size <= a.size(); size++) {
    for (int parts = 1; parts <= partCount; parts++) {
        for (int partSize = 1; partSize <= size; partSize++) {
            long long partPrice = getPartPrice(size - partSize, size - 1);
            res[size][parts] = min(res[size][parts], res[size - partSize][parts - 1] + partPrice);
        }
    }
}
return res[a.size()][partCount];
Поменяем порядок индексов и будем перебирать prevSize вместо partSize:
vector<vector<long long>> res(partCount + 1, vector<long long>(a.size() + 1, 1e18));
res[0][0] = 0;
for (int parts = 1; parts <= partCount; parts++) {
    for (int size = 1; size <= a.size(); size++) {
        for (int prevSize = 0; prevSize < size; prevSize++) {
            long long partPrice = getPartPrice(prevSize, size - 1);
            res[parts][size] = min(res[parts][size], res[parts - 1][prevSize] + partPrice);
        }
    }
}
return res[partCount][a.size()];
Будем сохранять лучший prevSize:
vector<vector<long long>> res(partCount + 1, vector<long long>(a.size() + 1, 1e18));
vector<vector<int>> bestPrevSize(partCount + 1, vector<int>(a.size() + 1));
res[0][0] = 0;
for (int parts = 1; parts <= partCount; parts++) {
    for (int size = 1; size <= a.size(); size++) {
        for (int prevSize = 0; prevSize < size; prevSize++) {
            long long partPrice = getPartPrice(prevSize, size - 1);
            if (res[parts][size] > res[parts - 1][prevSize] + partPrice) {
                res[parts][size] = res[parts - 1][prevSize] + partPrice;
                bestPrevSize[parts][size] = prevSize;
            }
        }
    }
}
return res[partCount][a.size()];
Условия применимости:
* Если делим на большее число частей тот же отрезок, последняя часть не должна начаться левее.
* Если делим на то же число частей больший отрезок, последняя часть не должна начаться левее.
bestPrevSize[parts - 1][size] <= bestPrevSize[parts][size]
bestPrevSize[parts][size] <= bestPrevSize[parts][size + 1]
Будем перебирать prevSize от bestPrevSize[parts - 1][size] до bestPrevSize[parts][size + 1]. Чтобы bestPrevSize[parts][size + 1] уже был вычислен, будем перебирать size по убыванию.
Сложность становится равной O(N^2).
vector<vector<long long>> res(partCount + 1, vector<long long>(a.size() + 1, 1e18));
vector<vector<int>> bestPrevSize(partCount + 1, vector<int>(a.size() + 1));
res[0][0] = 0;
for (int parts = 1; parts <= partCount; parts++) {
    for (int size = a.size(); size >= 1; size--) {
        int prevSizeL = bestPrevSize[parts - 1][size];
        int prevSizeR = size == a.size() ? size - 1 : min(size - 1, bestPrevSize[parts][size + 1]);
        for (int prevSize = prevSizeL; prevSize <= prevSizeR; prevSize++) {
            long long partPrice = getPartPrice(prevSize, size - 1);
            if (res[parts][size] > res[parts - 1][prevSize] + partPrice) {
                res[parts][size] = res[parts - 1][prevSize] + partPrice;
                bestPrevSize[parts][size] = prevSize;
            }
        }
    }
}
return res[partCount][a.size()];
* [https://peltorator.org/posts/knuth_yao_optimization/ peltorator.org — Оптимизация Кнута — Яо]
* [https://algorithmica.org/ru/dp-optimizations#оптимизация-кнута algorithmica.org — Пересчёт динамики по слоям. Оптимизация Кнута]
* [http://wiki.algocode.ru/index.php?title=%D0%9E%D0%BF%D1%82%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F_%D0%9A%D0%BD%D1%83%D1%82%D0%B0 algocode.ru — Оптимизация Кнута]
* [https://cp-algorithms.com/dynamic_programming/knuth-optimization.html cp-algorithms.com — Knuth's Optimization (динамика по подотрезкам)]
* [https://robert1003.github.io/2020/02/29/dp-opt-knuth.html robert1003.github.io — Knuth Optimization]
* [https://usaco.guide/adv/dp-more usaco.guide — Additional DP Optimizations and Techniques]
* [https://jeffreyxiao.me/blog/knuths-optimization Xiao J. — Knuth's Optimization]
== Оптимизация Кнута (подотрезок массива) ==
TBA
== Divide & Conquer-оптимизация (массив, число; N^2 * P → NlogN * P) ==
Решается задача «Разделить массив на partCount частей, минимизировав нечто». Паттерн — двумерная динамика, позиция в массиве + число.
Базовое решение за O(N^2 * P):
vector<vector<long long>> res(a.size() + 1, vector<long long>(partCount + 1, 1e18));
res[0][0] = 0;
for (int size = 1; size <= a.size(); size++) {
    for (int parts = 1; parts <= partCount; parts++) {
        for (int partSize = 1; partSize <= size; partSize++) {
            long long partPrice = getPartPrice(size - partSize, size - 1);
            res[size][parts] = min(res[size][parts], res[size - partSize][parts - 1] + partPrice);
        }
    }
}
return res[a.size()][partCount];
Поменяем порядок индексов и будем перебирать prevSize вместо partSize:
vector<vector<long long>> res(partCount + 1, vector<long long>(a.size() + 1, 1e18));
res[0][0] = 0;
for (int parts = 1; parts <= partCount; parts++) {
    for (int size = 1; size <= a.size(); size++) {
        for (int prevSize = 0; prevSize < size; prevSize++) {
            long long partPrice = getPartPrice(prevSize, size - 1);
            res[parts][size] = min(res[parts][size], res[parts - 1][prevSize] + partPrice);
        }
    }
}
return res[partCount][a.size()];
Вынесем расчёт строки для фиксированного parts в функцию:
void calc(vector<vector<long long>> &res, int parts, vector<long long> &p) {
    for (int size = 1; size < res[0].size(); size++) {
        for (int prevSize = 0; prevSize < size; prevSize++) {
            long long partPrice = getPartPrice(prevSize, size - 1);
            res[parts][size] = min(res[parts][size], res[parts - 1][prevSize] + partPrice);
        }
    }
}
vector<vector<long long>> res(partCount + 1, vector<long long>(a.size() + 1, 1e18));
res[0][0] = 0;
for (int parts = 1; parts <= partCount; parts++)
    calc(res, parts, p);
return res[partCount][a.size()];
Внутри функции вместо цикла по size сделаем рекурсию:
void rec(vector<vector<long long>> &res, int parts, int sizeL, int sizeR, vector<long long> &p) {
    if (sizeL > sizeR)
        return;
    int size = sizeL + (sizeR - sizeL) / 2;
    for (int prevSize = 0; prevSize < size; prevSize++) {
        long long partPrice = getPartPrice(prevSize, size - 1);
        res[parts][size] = min(res[parts][size], res[parts - 1][prevSize] + partPrice);
    }
    rec(res, parts, sizeL, size - 1, p);
    rec(res, parts, size + 1, sizeR, p);
}
vector<vector<long long>> res(partCount + 1, vector<long long>(a.size() + 1, 1e18));
res[0][0] = 0;
for (int parts = 1; parts <= partCount; parts++)
    rec(res, parts, 0, a.size(), p);
return res[partCount][a.size()];
В рекурсии будем сохранять лучший prevSize:
void rec(vector<vector<long long>> &res, int parts, int sizeL, int sizeR, vector<long long> &p) {
    if (sizeL > sizeR)
        return;
    int size = sizeL + (sizeR - sizeL) / 2;
    int bestPrevSize = 0;
    for (int prevSize = 0; prevSize < size; prevSize++) {
        long long partPrice = getPartPrice(prevSize, size - 1);
        if (res[parts][size] > res[parts - 1][prevSize] + partPrice) {
            res[parts][size] = res[parts - 1][prevSize] + partPrice;
            bestPrevSize = prevSize;
        }
    }
    rec(res, parts, sizeL, size - 1, p);
    rec(res, parts, size + 1, sizeR, p);
}
vector<vector<long long>> res(partCount + 1, vector<long long>(a.size() + 1, 1e18));
res[0][0] = 0;
for (int parts = 1; parts <= partCount; parts++)
    rec(res, parts, 0, a.size(), p);
return res[partCount][a.size()];
Заметим, что в рекурсивных вызовах можно перебирать prevSize только до bestPrevSize или только от bestPrevSize.
Сложность становится равной O(NlogN * P).
void rec(vector<vector<long long>> &res, int parts, int sizeL, int sizeR, int prevSizeL, int prevSizeR, vector<long long> &p) {
    if (sizeL > sizeR)
        return;
    int size = sizeL + (sizeR - sizeL) / 2;
    int bestPrevSize = prevSizeL;
    for (int prevSize = prevSizeL; prevSize <= prevSizeR && prevSize < size; prevSize++) {
        long long partPrice = getPartPrice(p, prevSize, size - 1);
        if (res[parts][size] > res[parts - 1][prevSize] + partPrice) {
            res[parts][size] = res[parts - 1][prevSize] + partPrice;
            bestPrevSize = prevSize;
        }
    }
    rec(res, parts, sizeL, size - 1, prevSizeL, bestPrevSize, p);
    rec(res, parts, size + 1, sizeR, bestPrevSize, prevSizeR, p);
}
vector<vector<long long>> res(partCount + 1, vector<long long>(a.size() + 1, 1e18));
res[0][0] = 0;
for (int parts = 1; parts <= partCount; parts++)
    rec(res, parts, 0, a.size(), 0, a.size(), p);
return res[partCount][a.size()];
* [https://algorithmica.org/ru/dp-optimizations#%D1%80%D0%B0%D0%B7%D0%B4%D0%B5%D0%BB%D1%8F%D0%B9-%D0%B8-%D0%B2%D0%BB%D0%B0%D1%81%D1%82%D0%B2%D1%83%D0%B9 algorithmica.org — Пересчёт динамики по слоям. Разделяй-и-властвуй]
* [http://wiki.algocode.ru/index.php?title=Divide%26Conquer_%D0%BE%D0%BF%D1%82%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F algocode.ru — Divide & Conquer-оптимизация]
* [https://cp-algorithms.com/dynamic_programming/divide-and-conquer-dp.html cp-algorithms.com — Divide and Conquer DP]
* [https://elevencompetingnodes.quora.com/DP-Divide-and-Conquer-Optimization Nasser M. — DP Divide and Conquer Optimization]
* [https://robert1003.github.io/2020/02/25/dp-opt-divide-and-conquer.html robert1003.github.io — Divide and Conquer DP]
* [https://usaco.guide/plat/DC-DP usaco.guide — Divide & Conquer DP]
* [https://jeffreyxiao.me/blog/divide-and-conquer-optimization Xiao J. — Divide and Conquer Optimization]
== Ссылки ==
== Ссылки ==
* [http://algorithmica.org/ru/dp-optimizations Algorithmica.org — Пересчёт динамики по слоям]
Теория:
* algocode.ru — Оптимизации динамики: [http://wiki.algocode.ru/index.php?title=%D0%9C%D0%BE%D0%BD%D0%BE%D1%82%D0%BE%D0%BD%D0%BD%D0%BE%D1%81%D1%82%D1%8C_%D1%82%D0%BE%D1%87%D0%BA%D0%B8_%D0%BF%D0%B5%D1%80%D0%B5%D0%B3%D0%B8%D0%B1%D0%B0 1], [http://wiki.algocode.ru/index.php?title=Divide%26Conquer_%D0%BE%D0%BF%D1%82%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F 2], [http://wiki.algocode.ru/index.php?title=%D0%9E%D0%BF%D1%82%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F_%D0%9A%D0%BD%D1%83%D1%82%D0%B0 3], [http://wiki.algocode.ru/index.php?title=Convex_hull_trick 4], [http://wiki.algocode.ru/index.php?title=%D0%94%D0%B5%D1%80%D0%B5%D0%B2%D0%BE_Li_Chao 5], [http://wiki.algocode.ru/index.php?title=%D0%9B%D1%8F%D0%BC%D0%B1%D0%B4%D0%B0-%D0%BE%D0%BF%D1%82%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F 6]
* [http://algorithmica.org/ru/dp-optimizations algorithmica.org — Пересчёт динамики по слоям]
* [http://codeforces.com/blog/entry/8219 Codeforces — Dynamic Programming Optimizations]
* algocode.ru — Оптимизации динамики: [http://wiki.algocode.ru/index.php?title=%D0%9C%D0%BE%D0%BD%D0%BE%D1%82%D0%BE%D0%BD%D0%BD%D0%BE%D1%81%D1%82%D1%8C_%D1%82%D0%BE%D1%87%D0%BA%D0%B8_%D0%BF%D0%B5%D1%80%D0%B5%D0%B3%D0%B8%D0%B1%D0%B0 1], [http://wiki.algocode.ru/index.php?title=%D0%9E%D0%BF%D1%82%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F_%D0%9A%D0%BD%D1%83%D1%82%D0%B0 2], [http://wiki.algocode.ru/index.php?title=Convex_hull_trick 3], [http://wiki.algocode.ru/index.php?title=%D0%94%D0%B5%D1%80%D0%B5%D0%B2%D0%BE_Li_Chao 4], [http://wiki.algocode.ru/index.php?title=%D0%9B%D1%8F%D0%BC%D0%B1%D0%B4%D0%B0-%D0%BE%D0%BF%D1%82%D0%B8%D0%BC%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F 5]
* [https://codeforces.com/blog/entry/8219 codeforces.com — Dynamic Programming Optimizations]
* [https://codeforces.com/blog/entry/47764 codeforces.com — Non-trivial DP Tricks and Techniques]
* [https://cp-algorithms.com/dynamic_programming/knuth-optimization.html cp-algorithms.com — Knuth's Optimization]
* [https://usaco.guide/adv/dp-more usaco.guide — Additional DP Optimizations and Techniques]
* [http://jeffe.cs.illinois.edu/teaching/algorithms/notes/D-faster-dynprog.pdf Erickson J. Advanced Dynamic Programming]
* Xiao J. [https://jeffreyxiao.me/blog/convex-hull-trick Convex Hull Trick], [https://jeffreyxiao.me/blog/knuths-optimization Knuth's Optimization]
* [http://maratona.ic.unicamp.br/MaratonaVerao2017/documents/dp.pdf Ахмедов М. Dynamic programming optimizations]
* [http://maratona.ic.unicamp.br/MaratonaVerao2017/documents/dp.pdf Ахмедов М. Dynamic programming optimizations]
* [http://acm.spbgu.ru/~sk1/mm/lections/zksh2017-dp/conspect.pdf Копелиович С. Лекция по динамике (ЗКШ 2017)]
* [http://acm.math.spbu.ru/~sk1/mm/lections/zksh2017-dp/conspect.pdf Копелиович С. Лекция по динамике (ЗКШ 2017)]
* [http://jeffe.cs.illinois.edu/teaching/algorithms/notes/D-faster-dynprog.pdf Erickson J. Advanced Dynamic Programming]
 
[[Категория:Динамическое программирование]]

Текущая версия от 20:24, 15 октября 2026

Оптимизация Кнута (массив, число; N^2 * P → N^2)

Решается задача «Разделить массив на partCount частей, минимизировав нечто». Паттерн — двумерная динамика, позиция в массиве + число.

Базовое решение за O(N^2 * P):

vector<vector<long long>> res(a.size() + 1, vector<long long>(partCount + 1, 1e18));
res[0][0] = 0;

for (int size = 1; size <= a.size(); size++) {
    for (int parts = 1; parts <= partCount; parts++) {
        for (int partSize = 1; partSize <= size; partSize++) {
            long long partPrice = getPartPrice(size - partSize, size - 1);
            res[size][parts] = min(res[size][parts], res[size - partSize][parts - 1] + partPrice);
        }
    }
}

return res[a.size()][partCount];

Поменяем порядок индексов и будем перебирать prevSize вместо partSize:

vector<vector<long long>> res(partCount + 1, vector<long long>(a.size() + 1, 1e18));
res[0][0] = 0;

for (int parts = 1; parts <= partCount; parts++) {
    for (int size = 1; size <= a.size(); size++) {
        for (int prevSize = 0; prevSize < size; prevSize++) {
            long long partPrice = getPartPrice(prevSize, size - 1);
            res[parts][size] = min(res[parts][size], res[parts - 1][prevSize] + partPrice);
        }
    }
}

return res[partCount][a.size()];

Будем сохранять лучший prevSize:

vector<vector<long long>> res(partCount + 1, vector<long long>(a.size() + 1, 1e18));
vector<vector<int>> bestPrevSize(partCount + 1, vector<int>(a.size() + 1));
res[0][0] = 0;

for (int parts = 1; parts <= partCount; parts++) {
    for (int size = 1; size <= a.size(); size++) {
        for (int prevSize = 0; prevSize < size; prevSize++) {
            long long partPrice = getPartPrice(prevSize, size - 1);
            if (res[parts][size] > res[parts - 1][prevSize] + partPrice) {
                res[parts][size] = res[parts - 1][prevSize] + partPrice;
                bestPrevSize[parts][size] = prevSize;
            }
        }
    }
}

return res[partCount][a.size()];

Условия применимости:

  • Если делим на большее число частей тот же отрезок, последняя часть не должна начаться левее.
  • Если делим на то же число частей больший отрезок, последняя часть не должна начаться левее.
bestPrevSize[parts - 1][size] <= bestPrevSize[parts][size]
bestPrevSize[parts][size] <= bestPrevSize[parts][size + 1]

Будем перебирать prevSize от bestPrevSize[parts - 1][size] до bestPrevSize[parts][size + 1]. Чтобы bestPrevSize[parts][size + 1] уже был вычислен, будем перебирать size по убыванию.

Сложность становится равной O(N^2).

vector<vector<long long>> res(partCount + 1, vector<long long>(a.size() + 1, 1e18));
vector<vector<int>> bestPrevSize(partCount + 1, vector<int>(a.size() + 1));
res[0][0] = 0;

for (int parts = 1; parts <= partCount; parts++) {
    for (int size = a.size(); size >= 1; size--) {
        int prevSizeL = bestPrevSize[parts - 1][size];
        int prevSizeR = size == a.size() ? size - 1 : min(size - 1, bestPrevSize[parts][size + 1]);

        for (int prevSize = prevSizeL; prevSize <= prevSizeR; prevSize++) {
            long long partPrice = getPartPrice(prevSize, size - 1);
            if (res[parts][size] > res[parts - 1][prevSize] + partPrice) {
                res[parts][size] = res[parts - 1][prevSize] + partPrice;
                bestPrevSize[parts][size] = prevSize;
            }
        }
    }
}

return res[partCount][a.size()];


Оптимизация Кнута (подотрезок массива)

TBA


Divide & Conquer-оптимизация (массив, число; N^2 * P → NlogN * P)

Решается задача «Разделить массив на partCount частей, минимизировав нечто». Паттерн — двумерная динамика, позиция в массиве + число.

Базовое решение за O(N^2 * P):

vector<vector<long long>> res(a.size() + 1, vector<long long>(partCount + 1, 1e18));
res[0][0] = 0;

for (int size = 1; size <= a.size(); size++) {
    for (int parts = 1; parts <= partCount; parts++) {
        for (int partSize = 1; partSize <= size; partSize++) {
            long long partPrice = getPartPrice(size - partSize, size - 1);
            res[size][parts] = min(res[size][parts], res[size - partSize][parts - 1] + partPrice);
        }
    }
}

return res[a.size()][partCount];

Поменяем порядок индексов и будем перебирать prevSize вместо partSize:

vector<vector<long long>> res(partCount + 1, vector<long long>(a.size() + 1, 1e18));
res[0][0] = 0;

for (int parts = 1; parts <= partCount; parts++) {
    for (int size = 1; size <= a.size(); size++) {
        for (int prevSize = 0; prevSize < size; prevSize++) {
            long long partPrice = getPartPrice(prevSize, size - 1);
            res[parts][size] = min(res[parts][size], res[parts - 1][prevSize] + partPrice);
        }
    }
}

return res[partCount][a.size()];

Вынесем расчёт строки для фиксированного parts в функцию:

void calc(vector<vector<long long>> &res, int parts, vector<long long> &p) {
    for (int size = 1; size < res[0].size(); size++) {
        for (int prevSize = 0; prevSize < size; prevSize++) {
            long long partPrice = getPartPrice(prevSize, size - 1);
            res[parts][size] = min(res[parts][size], res[parts - 1][prevSize] + partPrice);
        }
    }
}

vector<vector<long long>> res(partCount + 1, vector<long long>(a.size() + 1, 1e18));
res[0][0] = 0;

for (int parts = 1; parts <= partCount; parts++)
    calc(res, parts, p);

return res[partCount][a.size()];

Внутри функции вместо цикла по size сделаем рекурсию:

void rec(vector<vector<long long>> &res, int parts, int sizeL, int sizeR, vector<long long> &p) {
    if (sizeL > sizeR)
        return;

    int size = sizeL + (sizeR - sizeL) / 2;

    for (int prevSize = 0; prevSize < size; prevSize++) {
        long long partPrice = getPartPrice(prevSize, size - 1);
        res[parts][size] = min(res[parts][size], res[parts - 1][prevSize] + partPrice);
    }

    rec(res, parts, sizeL, size - 1, p);
    rec(res, parts, size + 1, sizeR, p);
}

vector<vector<long long>> res(partCount + 1, vector<long long>(a.size() + 1, 1e18));
res[0][0] = 0;

for (int parts = 1; parts <= partCount; parts++)
    rec(res, parts, 0, a.size(), p);

return res[partCount][a.size()];

В рекурсии будем сохранять лучший prevSize:

void rec(vector<vector<long long>> &res, int parts, int sizeL, int sizeR, vector<long long> &p) {
    if (sizeL > sizeR)
        return;

    int size = sizeL + (sizeR - sizeL) / 2;
    int bestPrevSize = 0;

    for (int prevSize = 0; prevSize < size; prevSize++) {
        long long partPrice = getPartPrice(prevSize, size - 1);
        if (res[parts][size] > res[parts - 1][prevSize] + partPrice) {
            res[parts][size] = res[parts - 1][prevSize] + partPrice;
            bestPrevSize = prevSize;
        }
    }

    rec(res, parts, sizeL, size - 1, p);
    rec(res, parts, size + 1, sizeR, p);
}

vector<vector<long long>> res(partCount + 1, vector<long long>(a.size() + 1, 1e18));
res[0][0] = 0;

for (int parts = 1; parts <= partCount; parts++)
    rec(res, parts, 0, a.size(), p);

return res[partCount][a.size()];

Заметим, что в рекурсивных вызовах можно перебирать prevSize только до bestPrevSize или только от bestPrevSize.

Сложность становится равной O(NlogN * P).

void rec(vector<vector<long long>> &res, int parts, int sizeL, int sizeR, int prevSizeL, int prevSizeR, vector<long long> &p) {
    if (sizeL > sizeR)
        return;

    int size = sizeL + (sizeR - sizeL) / 2;
    int bestPrevSize = prevSizeL;

    for (int prevSize = prevSizeL; prevSize <= prevSizeR && prevSize < size; prevSize++) {
        long long partPrice = getPartPrice(p, prevSize, size - 1);
        if (res[parts][size] > res[parts - 1][prevSize] + partPrice) {
            res[parts][size] = res[parts - 1][prevSize] + partPrice;
            bestPrevSize = prevSize;
        }
    }

    rec(res, parts, sizeL, size - 1, prevSizeL, bestPrevSize, p);
    rec(res, parts, size + 1, sizeR, bestPrevSize, prevSizeR, p);
}

vector<vector<long long>> res(partCount + 1, vector<long long>(a.size() + 1, 1e18));
res[0][0] = 0;

for (int parts = 1; parts <= partCount; parts++)
    rec(res, parts, 0, a.size(), 0, a.size(), p);

return res[partCount][a.size()];

Ссылки

Теория: