zoukankan      html  css  js  c++  java
  • [kuangbin带你飞]专题二十二 区间DP

    [kuangbin带你飞]专题二十二 区间DP

    ZOJ 3537 Cake

    判断闭包+最优三角划分

    注意是用求出的闭包来算转移

    #include <bits/stdc++.h>
    using namespace std;
    const int INF = 0x3f3f3f3f;
    int m,f[400][400],dp[400][400];
    struct Point{
        int x,y;
        Point(){}
        Point(int xx,int yy):x(xx),y(yy){}
        void read(){
            scanf("%d%d",&x,&y);
        }
        bool operator < (const Point &other)const{
            if(x == other.x)
                return y < other.y;
            return x < other.x;
        }
        Point operator - (const Point &other)const{
            return Point(x - other.x,y - other.y);
        }
    };
    Point p[400],ch[400];
    int n;
    double Cross(Point A,Point B){
        return A.x * B.y - A.y * B.x;
    }
    int ConvexHull(){
        sort(p,p+n);
        int cnt = 0;
        for(int i = 0; i < n; i++){
            while(cnt > 1 && Cross(ch[cnt-1] - ch[cnt-2],p[i] - ch[cnt-2]) <= 0) cnt--;
            ch[cnt++] = p[i];
        }
        int k = cnt;
        for(int i = n-2; i >= 0; i--){
            while(cnt > k && Cross(ch[cnt-1] - ch[cnt-2],p[i] - ch[cnt-2]) <= 0) cnt--;
            ch[cnt++] = p[i];
        }
        if(n > 1) cnt--;
        return cnt;
    }
    int calc(Point a,Point b){
        return (abs(a.x + b.x) * abs(a.y + b.y)) % m;
    }
    int main(){
        while(scanf("%d%d",&n,&m) != EOF){
            memset(p,0,sizeof(p));
            memset(ch,0,sizeof(ch));
            memset(f,0,sizeof(f));
            for(int i = 0; i < n; i++){
                p[i].read();
            }
            if(n == 3){
                puts("0");
                continue;
            }
            if(ConvexHull() < n){
                printf("I can't cut.
    ");
            }
            else{
                for(int i = 0; i < n; i++){
                    for(int j = i+2; j < n; j++){
                        f[i][j] = f[j][i] = calc(ch[i],ch[j]);
                    }
                }
                for(int i = 0; i < n; i++){
                    for(int j = 0; j < n; j++){
                        dp[i][j] = INF;
                    }
                    dp[i][(i+1)%n] = 0;
                }
                 for (int len = 3; len <= n; len++) {
                    for (int i = 0; i + len - 1 < n; i++) {
                        int j = i + len - 1;
                        for (int k = i + 1; k <= j - 1; k++) {
                            dp[i][j] = min(dp[i][j],dp[i][k]+dp[k][j]+f[i][k]+f[k][j]);
                        }
                    }
                }
                printf("%d
    ",dp[0][n-1]);
            }
        }
        return 0;
    }
    

    LightOJ 1422 Halloween Costumes

    大意:

    n个宴会,对于每一个宴会,都要穿一种礼服,礼服可以套着穿,但是脱了的不能再用,参加宴会必须按顺序来,从第一个到第N个,问参加这些宴会最少需要几件礼服

    思路:

    当c[j-1] = c[j] 时显然有dp[ i ] [ j ] = dp[ i ] [j-1]

    更新是更新当第k场舞会衣服与第j场衣服一致时,假设第k场衣服一直穿着

    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int N = 1e2 + 5;
    typedef long long LL;
    int t, a[N], dp[N][N], cases = 0;
    int main() {
        cin >> t;
        while (t--) {
            cases++;
            memset(dp, 0x3f, sizeof dp);
            int n;
            cin >> n;
            for (int i = 0; i < n; i++) {
                cin >> a[i];
            }
            for (int i = 0; i < n; i++) {
                dp[i][i] = 1;
            }
            for (int len = 2; len <= n; len++) {
                for (int i = 0; i + len - 1 < n; i++) {
                    int j = i + len - 1;
                    if (a[j] == a[j - 1])
                        dp[i][j] = dp[i][j - 1];
                    else
                        dp[i][j] = dp[i][j - 1] + 1;
                    for (int k = i; k < j; k++) {
                        if (a[k] == a[j]) {
                            dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j - 1]);
                        }
                    }
                }
            }
            printf("Case %d: %d
    ", cases, dp[0][n - 1]);
        }
        return 0;
    }
    

    POJ 2955 Brackets

    大意:

    给出一个字符串,问满足括号匹配的最长区间是多少

    满足括号匹配的有:

    (), [], (()), ()[], ()[()]
    

    思路:

    (s[i]==s[j])时,(dp[i][j]==dp[i+1][j-1]+2)

    然后直接转移就行

    #include <math.h>
    #include <stdio.h>
    #include <string.h>
    #include <algorithm>
    #include <cstdio>
    #include <iostream>
    #include <queue>
    #include <stack>
    #include <vector>
    using namespace std;
    const int N = 1e2 + 5;
    typedef long long LL;
    int dp[N][N];
    int main() {
        string s;
        while (cin >> s) {
            if (s == "end") break;
            int n = s.size();
            memset(dp, 0, sizeof dp);
            int res = 0;
            for (int len = 2; len <= n; len++) {
                for (int i = 0; i + len - 1 < n; i++) {
                    int j = i + len - 1;
                    if ((s[i] == '(' && s[j] == ')') ||
                        (s[i] == '[' && s[j] == ']'))
                        dp[i][j] = dp[i + 1][j - 1] + 2;
                    for (int k = i; k < j; k++) {
                        dp[i][j] = max(dp[i][k] + dp[k + 1][j], dp[i][j]);
                    }
                    res = max(res, dp[i][j]);
                }
            }
            cout << res << endl;
        }
        return 0;
    }
    

    CodeForces 149D Coloring Brackets

    大意:

    给出一个能被匹配的括号字符串,现在对这些括号进行涂色,要求满足:

    对于每一对配对的括号,有且只有一个括号染色

    相邻的括号,如果都染色了,颜色不能相同

    每个括号要么不染色,要么染红色,要么染绿色

    思路:

    首先利用栈对括号进行匹配

    利用dfs写区间dp,枚举两侧的颜色,然后进行转移

    如果当前区间的左右端点配对,那么直接进入下一层区间

    否则枚举左端点对应的配对的右括号,枚举全部的颜色即可

    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int N = 7e2 + 5;
    typedef long long LL;
    LL dp[N][N][5][5];
    string s;
    const LL mod = 1e9 + 7;
    int n, ne[N];
    stack<int> st;
    
    LL dfs(int l, int r, int c1, int c2) {
        LL res = 0;
        if (l >= r) return dp[l][r][c1][c2] = 0LL;
        if (dp[l][r][c1][c2] != -1) return dp[l][r][c1][c2];
        if (ne[l] == r) {  //遇到这个区间左右端点恰好匹配,那么左右端点的方案已经固定了
            if ((c1 == 0 && c2 != 0) || (c2 == 0 && c1 != 0)) {  //当前匹配必须合法
                if (l + 1 == r) return dp[l][r][c1][c2] = 1LL;
                for (int i = 0; i < 3; i++) {
                    for (int j = 0; j < 3; j++) {
                        if (((i == 0) || (c1 == 0) || (i != c1)) &&
                            ((j == 0) || (c2 == 0) || (j != c2))) {
                            res = (res + dfs(l + 1, r - 1, i, j)) % mod;
                        }
                    }
                }
            }
        } else {
            int k = ne[l];
            for (int i = 0; i < 3; i++) {
                for (int j = 0; j < 3; j++) {
                    if ((i == 0) || (j == 0) || (i != j)) {
                        res = (res + dfs(l, k, c1, i) * dfs(k + 1, r, j, c2) % mod) %mod;
                    }
                }
            }
        }
    
        return dp[l][r][c1][c2] = res;
    }
    
    int main() {
        cin >> s;
        memset(dp, -1, sizeof dp);
        for (int i = 0; i < s.size(); i++) {
            if (s[i] == '(')
                st.push(i);
            else {
                ne[st.top()] = i;
                st.pop();
            }
        }
        n = s.size();
        LL res = 0;
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                res = (res + dfs(0, n - 1, i, j)) % mod;
            }
        }
        cout << res << endl;
        return 0;
    }
    

    POJ 1651 Multiplication Puzzle

    大意:

    给出n个数,每次从没有取出的数中取出一个数,花费是这个数和两边的还没有取出来的数的乘积,最左边和最右边的数不能取,问最后花费最小是多少

    思路:

    枚举中点转移即可,注意转移的区间是 i,k 和 k , j,不是i,k 和 k+1 , j

    区间dp就是要认真分析区间边界啊...

    #include <math.h>
    #include <stdio.h>
    #include <string.h>
    #include <algorithm>
    #include <cstdio>
    #include <iostream>
    #include <queue>
    #include <stack>
    #include <vector>
    using namespace std;
    
    const int N = 1e2 + 5;
    typedef long long LL;
    int n;
    LL a[N], dp[N][N];
    int main() {
        cin >> n;
        for (int i = 0; i < n; i++) cin >> a[i];
        memset(dp, 0x3f, sizeof dp);
        for (int i = 0; i < n - 1; i++) dp[i][i + 1] = 0;
        for (int len = 3; len <= n; len++) {
            for (int i = 0; i + len - 1 < n; i++) {
                int j = i + len - 1;
                for (int k = i + 1; k < j; k++) {
                    dp[i][j] =
                        min(dp[i][j], dp[i][k] + dp[k][j] + a[i] * a[k] * a[j]);
                }
            }
            }
        cout << dp[0][n - 1] << endl;
        return 0;
    }
    

    ZOJ 3469 Food Delivery

    大意:

    在X-轴上有一个餐厅,以及有n个点要送外卖。餐厅坐标为x,工人的速度为(v^{-1}),也就是 1/v 米每分钟。给出n个点的x坐标和b,外卖没送到,客户就会不愉快,每一分钟的不愉快指数增加b。问怎么样的送货策略,使得所有客户的总不愉悦指数和最小。

    思路:

    可以推出,当需要送区间l到r的两端的外卖时,必然是从区间l到r-1或者l+1到r转移过来,也就是说区间内部的点已经送过了,不然他会先送区间内部的点

    但是员工肯定要么是在区间左端点要么是在区间右端点,所以需要多开一维维护是在左边还是在右边

    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int N = 1e3 + 5;
    typedef long long LL;
    int n, v, x;
    struct node {
        int pos, b;
    } a[N];
    int dp[N][N][2], sum[N];
    bool cmp(node a, node b) { return a.pos < b.pos; }
    int main() {
        while (scanf("%d%d%d", &n, &v, &x) != EOF) {
            for (int i = 1; i <= n; i++) scanf("%d%d", &a[i].pos, &a[i].b);
            n++;
            a[n].pos = x, a[n].b = 0;
            sort(a+1, a + n + 1,cmp);
            int pos = 0;
            for (int i = 1; i <= n; i++) {
                if (a[i].pos == x) {
                    pos = i;
                    break;
                }
            }
            for (int i = 1; i <= n; i++) {
                sum[i] = sum[i - 1] + a[i].b;
            }
            memset(dp, 0x3f, sizeof dp);
            for (int i = pos; i >=1 ; i--) {
                for (int j = pos; j <= n; j++) {
                    if (i == j) {
                        dp[i][j][0] = dp[i][j][1] = 0;
                        continue;
                    }
                    int mid = sum[i - 1] - sum[0] + sum[n] - sum[j];
                    dp[i][j][0] = min(dp[i][j][0],dp[i + 1][j][0] + (mid+a[i].b) * (a[i + 1].pos - a[i].pos));
                    dp[i][j][0] = min(dp[i][j][0],dp[i + 1][j][1] + (mid+a[i].b) * (a[j].pos - a[i].pos));
                    dp[i][j][1] = min(dp[i][j][1],dp[i][j-1][0] + (mid+a[j].b) * (a[j].pos - a[i].pos));
                    dp[i][j][1] = min(dp[i][j][1],dp[i][j-1][1] + (mid+a[j].b) * (a[j].pos - a[j-1].pos));
                }
                
            }
            cout << min(dp[1][n][0], dp[1][n][1]) * v << endl;
        }
        return 0;
    }
    

    HDU 4283 You Are the One

    大意:

    有n个人,v [ i ] 表示每个人的愤怒值,现在他们依次上台表演,如果i是第k个上台,那么他的愤怒值为(k-1)* v[i] 现在有一个栈,可以让一些人进栈,让后面的人先上台表演,这样可以改变部分顺序,求所有人最小的愤怒值和

    思路:

    我们考虑区间[l,r],如果第一个人是该区间中第k个上场的,那么区间[l+1,r]中的人应当先于第一个上场,区间[l+k+1,r]中的人必定要在第一个人之后上场,这样一个区间就被划分成了两个区间。

    dp[ i + 1 ] [ i + k - 1]表示前 k - 1个人的沮丧值,a[i] * (k-1)表示第i个人的沮丧值,而i+k到j的这些人由于出场位置都增加了K,所以总的沮丧值增加了k * (sum[j] - sum[i+k-1])

    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int N = 1e2 + 5;
    typedef long long LL;
    int n, dp[N][N], t, a[N], sum[N], cases;
    int main() {
        cin >> t;
        while (t--) {
            cases++;
            cin >> n;
            for (int i = 1; i <= n; i++) cin >> a[i], sum[i] = sum[i - 1] + a[i];
            memset(dp, 0, sizeof dp);
            for (int len = 1; len <= n; len++) {
                for (int i = 1; i + len - 1 <= n; i++) {
                    int j = i + len - 1;
                    dp[i][j] = 0x3f3f3f3f;
                    if (len==1) {
                        dp[i][j] = 0;
                        continue;
                    }
                    for (int k = 1; k <= len; k++) {
                        dp[i][j] =
                            min(dp[i][j], dp[i + 1][i + k - 1] + a[i] * (k - 1) +dp[i+k][j]+
                                              k * (sum[j] - sum[i + k-1]));
                    }
                }
            }
            printf("Case #%d: %d
    ", cases, dp[1][n]);
        }
        return 0;
    }
    

    HDU 2476 String painter

    大意:

    给出两个字符串s1,s2。对于每次操作可以将 s1 串中的任意一个子段变成另一个字符。问最少需要多少步操作能将s1串变为s2串。

    思路:

    这个题先考虑怎么由空串转化s2,
    f [ i ] [ j ] 表示从空串到s2最少的次数,
    则有f[ i ] [ j ] = s [ i + 1 ] [ j ] +1,
    若[i + 1, j ]存在一个 k ,使s2[ i ]==s2[ k ],则f[ i ] [ j ]=min{f[i+1] [k ]+f[ k+1] [ j ] },
    也就是说i和k用同一次变换

    然后再考虑把s1刷成s2的代价
    设sum[i]表示把s1[1,i]刷成s2[1,i]的次数
    当s1[i]==s2[i]时,可以不刷,显然sum[i]=sum[i-1]
    否则,在区间内找最小次数sum[i]=min(sum[j] + f [j+1] [i])

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 1e3 + 10;
    
    int n, m;
    
    int f[N][N], sum[N];
    
    char s[N], t[N];
    
    int main() {
        while (cin >> s) {
            cin >> t;
            memset(f, 0, sizeof f);
            memset(sum, 0, sizeof sum);
            int len = strlen(s);
            for (int i = 0; i < len; ++i) f[i][i] = 1;
            for (int i = 0; i < len; ++i)
                for (int j = i - 1; j >= 0; --j) {
                    f[j][i] = f[j + 1][i] + 1;
                    for (int k = j + 1; k <= i; ++k)
                        if (t[j] == t[k])
                            f[j][i] = min(f[j][i], f[j + 1][k] + f[k + 1][i]);
                }
            for (int i = 0; i < len; ++i) sum[i] = f[0][i];
            if (s[0] == t[0]) sum[0] = 0;
            for (int i = 1; i < len; ++i) {
                if (s[i] == t[i])
                    sum[i] = min(sum[i], sum[i - 1]);
                else
                    for (int j = 0; j < i; ++j)
                        sum[i] = min(sum[i], sum[j] + f[j + 1][i]);
            }
            cout << sum[len - 1] << endl;
        }
    }
    
  • 相关阅读:
    <魔域>按键精灵脚本
    Windows下Java环境变量配置
    JDBC简单范例
    迅雷高速通道被举报无法下载问题
    wifi入侵思路
    连接WiFi工具类
    ActionBar+Fragment实现顶部标签页
    Fragment的基本用法
    opencv-python识别人脸
    string.Join 拼接在sql中特殊处理
  • 原文地址:https://www.cnblogs.com/dyhaohaoxuexi/p/14435141.html
Copyright © 2011-2022 走看看