Algorithm Improvement - Dynamic Programming - Interval DP

AcWing 1068. Merging of circular stones [Interval DP + circular interval problem]

The most classic interval dp problem, enumerate len first and then enumerate the left and right endpoints. The reason for this is that each state can be represented without missing each state.

#include <iostream>
#include <cstring>
const int N = 400 + 10, INF = 0x3f3f3f3f;//因为是环形我们把它拓展成一个链了
int g[N][N], f[N][N];
int w[N], s[N];
int n;

void solve()
{
    
    
    //input
    std::cin >> n;
    for(int i = 1; i <= n; ++ i) std::cin >> w[i], w[i + n] = w[i];
    
    //预处理前缀和,快速获得一个区间内的石子被合并的得分
    //从这里我们可以发现,我们处理的前缀和是这样的:s[r] - s[l] = w[l + 1] + w[l + 2] + ... + w[r]
    for (int i = 1; i <= n << 1; ++ i) s[i] = s[i - 1] + w[i];
    
    //dp, 
    //dp初始化
    memset(f, 0x3f, sizeof f);
    memset(g, - 0x3f, sizeof g);
    for (int len = 1; len <= n; ++ len)
        //for (int l = 1, r = l + len - 1; r <= n << 1; ++ l) 会segemention fault
        for (int l = 1, r; r = l + len - 1, r <= n << 1; ++ l)
        {
    
    
            if (len == 1) f[l][l] = g[l][l] = 0;//如果长度为1就用来初始化
            else 
            {
    
    //如果长度不为1
                for (int k = l; k + 1 <= r; ++ k)
                {
    
    
                    f[l][r] = std::min(f[l][r], f[l][k] + f[k + 1][r] + s[r] - s[l - 1]);
                    g[l][r] = std::max(g[l][r], g[l][k] + g[k + 1][r] + s[r] - s[l - 1]);
                }
            }
        }
    
    int minV = INF, maxV = -INF;
    for (int l = 1; l <= n; ++ l)
    {
    
    
        minV = std::min(minV, f[l][l + n - 1]);
        maxV = std::max(maxV, g[l][l + n - 1]);
    }
    std::cout << minV << std::endl << maxV;
    return ;
}

int main()
{
    
    
    solve();    
    return 0;
}

AcWing 320. Energy Necklace【Division DP】

The code template for this question is the code template for interval dp. But
Insert image description here
to be honest, I didn’t understand this paragraph.

#include <iostream>
#include <cstring>

const int N = 100, M = (N << 1) + 10, INF = 0x3f3f3f3f;

int w[M], f[M][M];

int n;
void solve()
{
    
    
    //input
    std::cin >> n;
    for (int i = 1; i <= n; ++ i) std::cin >> w[i], w[i + n] = w[i];
    
    //dp
    memset(f, -0x3f, sizeof f);
    
    for (int len = 2; len <= n + 1; ++ len)
        for (int l = 1, r; (r = l + len - 1) <= n << 1; ++ l)
            if (len == 2) f[l][r] = 0;//本题可以不用初始化,因为不存在负数的状态值,因此0就是最小的
            else 
            {
    
    
                for (int k = l + 1; k + 1 <= r; ++ k)
                {
    
    
                    f[l][r] = std::max(f[l][r], f[l][k] + f[k][r] + w[l] * w[k] * w[r]);// f[l][k] + f[k][r] + w[l] * w[k] * w[r]可以看出为什么我们我们要遍历n + 1
                }
            }
    
    int ans = 0;
    for (int l = 1; l <= n; ++ l)
    {
    
    
        ans = std::max(ans, f[l][l + n]);
    }
    std::cout << ans;
    return ;
}

int main()
{
    
    
    solve();   
    return 0;
}

AcWing 479. Bonus Binary Tree[Memorized Search]

What we should learn about this question is how to record our plan during the DP decision-making (assignment) process.
At the same time, this question can also be passed using the idea of ​​memorized search, but I passed it because I didn't know what memorized search is.

#include <iostream>

const int N = 31;

int f[N][N], g[N][N];
int w[N];
int n;

//这个前序遍历的输出就是要想像一下当前(l,r)是一个经过中序遍历过的序列,
//我们在dp的过程中用g[l][r]记录我们决策f[l][r]时 l - r区间的根结点,
//那么整棵树是以g[l][r]为根结点的,由中序遍历的特点,左边的树的区间就是 (l, g[l][r] - 1),右边的树的区间就是(g[l][r] + 1,  r)
void dfs(int l, int r)
{
    
    
    if (l > r) return ;
    else 
    {
    
    
        std::cout << g[l][r] << " ";
        dfs(l, g[l][r] - 1);
        dfs(g[l][r] + 1, r);
    }
}
void solve()
{
    
    
    std::cin >> n;
    for (int i = 1; i <= n; ++ i) std::cin >> w[i];
    
    for (int len = 1; len <= n; ++ len)
    {
    
    
        for (int l = 1, r; l + len - 1 <= n; ++ l)
        {
    
    
            r = l + len - 1;
            if (len == 1) f[l][r] = w[l], g[l][r] = l;
            else 
            {
    
    
                for (int k = l; k < r; ++ k)
                {
    
       //  left表示左子树区间的值
                    int left = k == l ? 1 : f[l][k - 1];//根节点为k的时候如果根节点和枚举的这棵树的左区间左端点重合了,说明左子树为空
                    int right = k == r ? 1 : f[k + 1][r];
                    int score = left * right + w[k];
                    if (f[l][r] < score)
                    {
    
    
                        f[l][r] = score;
                        g[l][r] = k;
                    }
                }
            }
        }
    }
    
    std::cout << f[1][n] << std::endl;
    dfs(1, n);
    return ;
}

int main()
{
    
    
    solve();
    return 0;
}

AcWing 1069. Division of convex polygons [interval dp + high precision]

#include <iostream>
#include <vector>

using namespace std;

typedef long long LL;
const int N = 55;
int w[N];

vector<int> f[N][N];
int n;

bool cmp(vector<int> a, vector<int> b)
{
    
    
    if (a.size() != b.size()) return a.size() < b.size();
    else 
    {
    
    
        for (int i = a.size() - 1; i >= 0; -- i)//下面说了,这里的高精度模版全是反的,因此比较的时候也反着比
        {
    
    
            if (a[i] != b[i]) return a[i] < b[i];
        }
    }
    return true;
}

vector<int> add(vector<int> a, vector<int> b)
{
    
    
    vector<int> c;
    int t = 0;
    for (int i = 0; i < a.size() || i < b.size(); ++ i)
    {
    
    
        if (i < a.size()) t += a[i];
        if (i < b.size()) t += b[i];
        c.push_back(t % 10);
        t /= 10;
    }
    while (t) c.push_back(t % 10), t /= 10;
    return c;
}

vector<int> mul(vector<int> a, LL b)
{
    
    
    vector<int> c;
    LL t = 0;
    for (int i = 0; i < a.size(); ++ i)
    {
    
    
        t += a[i] * b;
        c.push_back(t % 10);
        t /= 10;
    }
    while (t) c.push_back(t % 10), t /= 10;
    return c;
}

void solve()
{
    
    
    std::cin >> n;
    for (int i = 1; i <= n; ++ i) std::cin >> w[i];
    //dp;
    for (int len = 3; len <= n ; ++ len)
    {
    
    
        for (int l = 1, r; (r = l + len - 1) <= n; ++ l)
        {
    
    
            for (int k = l + 1; k < r; ++ k)//k从l + 1开始,因为l,k,r是三角形的三个顶点,肯定不能重合
            {
    
    
                auto newVal = mul(mul({
    
    w[l]}, w[k]), w[r]);
                newVal = add(add(newVal, f[l][k]), f[k][r]);
                if (f[l][r].empty() || cmp(newVal, f[l][r])) f[l][r] = newVal;
            }
        }
    }
    
    auto res = f[1][n];
    for (int i = res.size() - 1; i >= 0; --i) std::cout << res[i];//本题的高精度的模版全是反的,我就反着算,输出的时候也反转,负负得正
    return ;
}
int main()
{
    
    
    solve();
    return 0;
}

AcWing 321. Checkerboard segmentation [memory search/two-dimensional interval DP]

When traversing this question, the subscript starts from 1, and the array size needs to be set to 9, not 8. It was always set to 8 before, but I seem to know what memorized search is, and it should be a type of pruning.

#include <iostream>
#include <cstring>
#include <cmath>


const int N = 16;

double f[N][9][9][9][9];
double X;//平均数
int s[9][9], n;

double get(int x1, int y1, int x2, int y2)
{
    
    
    double delta = s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1];
    delta -= X;
    return delta * delta;
}


double dp(int k, int x1, int y1, int x2, int y2)
{
    
    
    if (f[k][x1][y1][x2][y2] >= 0) return f[k][x1][y1][x2][y2]; //记忆化搜索
    if (k == n) return f[k][x1][y1][x2][y2] = get(x1, y1, x2, y2);  //更新初始状态

    double t = 1e9; //初始化为无穷大
    for (int i = x1; i < x2; i ++ ) //横着切
    {
    
    
        t = std::min(t, dp(k + 1, x1, y1, i, y2) + get(i + 1, y1, x2, y2));
        t = std::min(t, dp(k + 1, i + 1, y1, x2, y2) + get(x1, y1, i, y2));
    }
    for (int i = y1; i < y2; i ++ ) //竖着切
    {
    
    
        t = std::min(t, dp(k + 1, x1, y1, x2, i) + get(x1, i + 1, x2, y2));
        t = std::min(t, dp(k + 1, x1, i + 1, x2, y2) + get(x1, y1, x2, i));
    }
    return f[k][x1][y1][x2][y2] = t;
}



void solve()
{
    
    
    //input
    std::cin >> n;
    for (int i = 1; i <= 8; ++ i)
    {
    
    
        for (int j = 1; j <= 8; ++ j)
            std::cin >> s[i][j];
    }
    
    //预处理
    for (int i = 1; i <= 8; ++ i)
    {
    
    
        for (int j = 1; j <= 8; ++ j)
            s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
    }
    X = (double)(s[8][8]) / n;
    
    //dp
    memset(f, -1, sizeof f);
    double res = dp(1, 1, 1, 8, 8);
    //output
    printf("%.3lf", sqrt(res / n));
    return ;
}

int main()
{
    
    
    solve();
    return 0;
}

Guess you like

Origin blog.csdn.net/chirou_/article/details/131836272