zoukankan      html  css  js  c++  java
  • KB专题:区间DP专辑

    自从多校后心憔悴啊,发现DP还是太水了,有一场的区间DP竟然不会做,咳,果然是赤裸裸的水军。

    花了几天时间写了几道区间DP的题目,大部分都是水题,然后和以前的合并起来就是KB区间DP这个8 + 1道题的专辑,大家可以试着AK。

    区间DP是一类在区间上进行动态规划的最优问题,一般是根据问题设出一个表示状态的dp,可以是二维的也可以是三维的,一般情况下为二维。然后将问题划分成两个子问题,也就是一段区间分成左右两个区间,然后将左右两个区间合并到整个区间,或者说局部最优解合并为全局最优解,然后得解。

    这类DP可以用常规的for循环来写,也可以用记忆化搜索来写,个人更倾向于记忆化搜索写法,因为这种写法相当易懂,要什么值你直接去记忆化搜索一下就ok了,随叫随到随用啊。

    1、ZOJ 3537 Cake

    第一次写的 凸包结合 DP,调了好久,

    问题属于经典的三角剖分

    区间DP状态转移方程:

    [f[i][j] = min(f[i][k] + f[k][j] + cost[i][k] + cost[k][j] ]

    其中 (j >= i+ 3,i+1<=k<=j-1,cost[i][k]) 为连一条 (i)(k) 的线的费用

    详细题解:Here

    2、LightOJ 1422 Halloween Costumes

    本题不可以直接模拟栈是因为,不知道当前的衣服本来就有还是需要新穿一件。

    初始化DP为一直穿衣服

    for (int i = 1; i <= n; ++i) for (int j = i; j <= n; ++j) 
        f[i][j] = f[i][j - 1] + (j == i || a[j] != a[j - 1]);
    

    然后从按区间DP板子,枚举 (k) ,只要 (a[k])(a[j]) 相同说明,是可以把从 (k+1)(j-1) 的所有衣服都脱掉,然后 (k) 天的这件衣服,直接在第 (j) 天参加聚会,就不需要穿新的衣服。从小区间依次递推到大区间,就可以得到正确的答案。

    [f[i][j] = f[i][j - 1] (a[i] == a[j]) \ f[i][j] = min(f[i][k] + f[k + 1][j]) (a[i] == a[k] and ile kle j) ]

    const int N = 110;
    int a[N], n, Case, f[N][N];
    int main() {
        cin.tie(nullptr)->sync_with_stdio(false);
        int _; for (cin >> _; _--;) {
            cin >> n;
            for (int i = 1; i <= n; ++i) cin >> a[i];
            for (int i = 1; i <= n; ++i)
                for (int j = i; j <= n; ++j) f[i][j] = f[i][j - 1] + (j == i || a[j] != a[j - 1]);
            for (int len = 1; len <= n; ++len)
                for (int i = 1; i + len <= n; ++i) {
                    int j = i + len;
                    for (int k = i; k + 1 <= j; ++k)
                        if (a[k] == a[j]) f[i][j] = min(f[i][j], f[i][k] + f[k + 1][j - 1]);
                        else f[i][j] = min(f[i][j], f[i][k] + f[k + 1][j - 1] + 1);
                }
            cout << "Case " << ++Case << ": " << f[1][n] << "
    ";
            memset(f, 0, sizeof(f));
        }
    }
    

    3、POJ 2955 Brackets

    先初始化一下匹配括号,一对匹配的括号肯定是他内部的答案加上2,然后再考虑一坨括号能不能由两坨括号拼接而成,如果可以,那么取他们的最大值。简而言之,本题就是两个条件,逐步去实现他们即可。

    const int N = 110;
    int f[N][N];
    string s;
    bool match(char a, char b) {
        if ((a == '(' && b == ')') || (a == '[' && b == ']')) return true;
        return false;
    }
    int main() {
        while (cin >> s) {
            if (s == "end") break;
            memset(f, 0, sizeof(f));
            int n = int(s.size());
            for (int len = 1; len < s.size(); ++len)
                for (int i = 0; i + len < s.size(); ++i) {
                    int j = len + i;
                    if (match(s[i], s[j]))
                        f[i][j] = f[i + 1][j - 1] + 2;
                    for (int p = i; p <= j; ++p)
                        f[i][j] = max(f[i][j], f[i][p] + f[p + 1][j]);
                }
            cout << f[0][s.size() - 1] << "
    ";
        }
    }
    

    4、CF 149 D Coloring Brackets

    这题是上一个题目的升级版。
    题面有三个要求:
    1.匹配的括号必须涂其中一个并且不能都涂
    2.相邻两个括号不能都染色,但是都可以不染色

    const int mod = 1e9 + 7, N = 800;
    string s;
    stack<int>st;
    ll m[N], f[N][N][3][3], n;
    ll dfs(int a, int b, int c, int d) {
        //a表示左端点位置 b表示右端点位置 c表示左端点颜色 d表示右端点颜色
        //0表示没涂色 1表示涂了一种颜色 2表示涂了另一种颜色
        ll res = 0;
        if (b <= a || f[a][b][c][d] != -1) { //如果区间已经被分的不能再分,或者上次已经算过,返回
            if (b <= a)//只有一种方案
                return 1;
            else
                return f[a][b][c][d];
        }
        if (b == m[a]) { //遇到这个区间左右端点恰好匹配 那么左右端点的方案已经固定了 向内部推一层
            if (c != 1) res += dfs(a + 1, b - 1, 1, 0) % mod;
            //如果左端点颜色不为1,则内部相邻的左端点颜色可以为1,左端点涂了色,右端点一定不能涂色
            if (d != 1) res += dfs(a + 1, b - 1, 0, 1) % mod;
            //如果右端点颜色不为1,则同理
            if (c != 2) res += dfs(a + 1, b - 1, 2, 0) % mod;
            //如果左端点颜色不为2,则同理
            if (d != 2) res += dfs(a + 1, b - 1, 0, 2) % mod;
            //如果右端点颜色不为2,则同理
        } else {
            //这个区间左右端点不匹配,则有四种情况
            res += (dfs(a + 1, m[a] - 1, 0, 1) * dfs(m[a] + 1, b, 1, d)) % mod;
            res += (dfs(a + 1, m[a] - 1, 0, 2) * dfs(m[a] + 1, b, 2, d)) % mod;
            ll p = dfs(m[a] + 1, b, 0, d);
            if (c != 1) res += (p * dfs(a + 1, m[a] - 1, 1, 0)) % mod;
            if (c != 2) res += (p * dfs(a + 1, m[a] - 1, 2, 0)) % mod;
        }
        f[a][b][c][d] = res % mod;//记录已经算好的
        return res % mod;
    }
    int main() {
        // cin.tie(nullptr)->sync_with_stdio(false);
        while (getline(cin, s)) {
            n = s.size();
            for (int i = 0; i < n; ++i) {
                if (s[i] == '(') st.push(i);
                else {
                    m[st.top()] = i;
                    st.pop();
                }
            }
            memset(f, -1, sizeof(f)); // 进行记忆化搜索之前,将dp数组置为-1,避免有0的方案出现却未被计算
            cout << (dfs(0, n - 1, 3, 3)) << "
    ";
        }
    }
    

    5、1651 Multiplication Puzzle

    由于题目说了,最左最右是不可以取走的,那么我们只用对第2到n-1这个区间的数进行讨论即可。
    对于样例模拟一下拿数,拿数有以下顺序:

    2 3 4
    2 4 3
    3 2 4
    3 4 2
    4 2 3
    4 3 2
    可见,拿数的方式并不一定是连续的。
    所以,先初始化一下长度为1的区间,答案就是他本身乘以他旁边的两个数,合并区间的时候可能是两个连续的区间合并,也有可能是两个断开的区间再把断点对应的得分带上(断点位置最后拿),即可满足无后效性

    const int N = 110;
    ll f[N][N], a[N], n;
    int main() {
        cin >> n;
        for (int i = 1; i <= n; ++i) cin >> a[i];
        for (int i = 2; i <= n - 1; ++i) f[i][i] = a[i] * a[i - 1] * a[i + 1];
        for (int len = 1; len <= n; ++len)
            for (int i = 2; i + len <= n; ++i) {
                int j = len + i;
                f[i][j] = min(f[i + 1][j] + a[i] * a[i - 1] * a[j + 1], f[i][j - 1] + a[j] * a[i - 1] * a[j + 1]);
                for (int k = i + 1; k <= j - 1; ++k)
                    f[i][j] = min(f[i][j], f[i][k - 1] + f[k + 1][j] + a[k] * a[i - 1] * a[j + 1]);
            }
        cout << f[2][n - 1] << "
    ";
    }
    

    6、ZOJ 3469 Food Delivery

    可以把所有点分为两组,一组是在起点左边的,一组是在起点右边的,用f[i][j][k]表示左边的送完了i个,右边的送完了j个,当前停留在k组(0表示左边那组,1表示右边那组),然后用记忆化搜索来进行递推

    const int N = 1e3 + 10, inf = 0x3f3f3f3f;
    struct node {int x, b;} pz[N], py[N]; //左边的点靠右排前
    bool cmp(node a, node b) {return a.x < b.x;}
    //左边的点走了第几个 右边的点走了第几个 如今停留在右边还是左边
    ll f[N][N][2];
    ll dp(int i, int j, int k) {
        if (f[i][j][k] != -1) return f[i][j][k];
        ll ans = inf;
        if (k == 0) { // 往左边
            if (i != 0) {
                int tmp = pz[i].b + py[j + 1].b;//所有还未走的的人的愤怒值之和
                //cout << tmp << endl;
                ans = min(ans, dp(i - 1, j, 0) + (pz[i].x - pz[i - 1].x) * tmp);
                ans = min(ans, dp(i - 1, j, 1) + (pz[i].x + py[j].x) * tmp);
            }
        } else {
            if (j != 0) {
                int tmp = pz[i + 1].b + py[j].b;
                ans = min(ans, dp(i, j - 1, 1) + (py[j].x - py[j - 1].x) * tmp);
                ans = min(ans, dp(i, j - 1, 0) + (pz[i].x + py[j].x) * tmp);
            }
        }
        return f[i][j][k] = ans;
    }
    int main() {
        cin.tie(nullptr)->sync_with_stdio(false);
        int n, v, x;
        while (cin >> n >> v >> x) {
            int idz = 0, idy = 0;
            for (int i = 1; i <= n; ++i) {
                int tx, tb;
                cin >> tx >> tb;
                //直接存储相对位置
                if (tx < x) pz[++idz].x = x - tx, pz[idz].b = tb;
                if (tx > x) py[++idy].x = tx - x, py[idy].b = tb;
            }
            sort(pz + 1, pz + 1 + idz, cmp);
            sort(py + 1, py + 1 + idy, cmp);
            py[idy + 1].b = 0;
            pz[idz + 1].b = 0;
            for (int i = idy; i >= 1; --i) py[i].b += py[i + 1].b;
            for (int i = idz; i >= 1; --i) pz[i].b += pz[i + 1].b;
            memset(f, -1, sizeof(f));
            f[0][0][0] = f[0][0][1] = 0;
            cout << (v * min(dp(idz, idy, 0), dp(idz, idy, 1))) << "
    ";
        }
    }
    

    7、HDU 4283 You Are the One (较难)

    8、Sdut 不老的传说问题 (较难)

    9、HDU String painter

    The desire of his soul is the prophecy of his fate
    你灵魂的欲望,是你命运的先知。

  • 相关阅读:
    反转链表 16
    CodeForces 701A Cards
    hdu 1087 Super Jumping! Jumping! Jumping!(动态规划)
    hdu 1241 Oil Deposits(水一发,自我的DFS)
    CodeForces 703B(容斥定理)
    poj 1067 取石子游戏(威佐夫博奕(Wythoff Game))
    ACM 马拦过河卒(动态规划)
    hdu 1005 Number Sequence
    51nod 1170 1770 数数字(数学技巧)
    hdu 2160 母猪的故事(睡前随机水一发)(斐波那契数列)
  • 原文地址:https://www.cnblogs.com/RioTian/p/15129825.html
Copyright © 2011-2022 走看看