动态规划专题之线性dp

POJ2279 Mr. Young's Picture Permutations
 
有N个学生合影,站成左对齐的k排,每行分别有N1,N2…NK个人,第一排站最后,第k排站之前。
学生身高依次是1…N。在合影时候要求每一排从左到右递减,每一列从后面到前也递减,一共有多少总方案

Input

输入
每组测试数据包含两行。第一行给出行数k
第二行包含从后到前(n1,n2,…,nk)的行的长度,作为由单个空格分隔的十进制整数。
问题数据以0结束。
N<=30, k<=5;

Output

输出
输出每组数据的方案数

Sample Input

1
30
5
1 1 1 1 1
3
3 2 1
4
5 3 3 1
5
6 5 4 3 2
2
15 15
0

Sample Output

1
1
16
4158
141892608
9694845
#include
#include
#include
using namespace std;
const int N = 31;
typedef long long LL;
int a[N];
int main() {
    int k;
    while (scanf("%d", &k) && k) {
        for (int i = 0; i < k; i++) {
            scanf("%d", &a[i]);
        }
        for (int i = k; i < 5; i++)
            a[i] = 0;
        LL dp[a[0] + 1][a[1] + 1][a[2] + 1][a[3] + 1][a[4] + 1];
        memset(dp, 0, sizeof(dp));
        dp[0][0][0][0][0] = 1;
        for (int a1 = 0; a1 <= a[0]; a1++) {
            for (int a2 = 0; a2 <= a[1]; a2++) {
                for (int a3 = 0; a3 <= a[2]; a3++) {
                    for (int a4 = 0; a4 <= a[3]; a4++) {
                        for (int a5 = 0; a5 <= a[4]; a5++) {
                            if (a1 < a[0]) {
                                dp[a1 + 1][a2][a3][a4][a5] += dp[a1][a2][a3][a4][a5];
                            }
                            if (a2 < a[1] && a1 > a2) {
                                dp[a1][a2 + 1][a3][a4][a5] += dp[a1][a2][a3][a4][a5];
                            }
                            if (a3 < a[2] && a2 > a3 && a1 >= a2) {
                                dp[a1][a2][a3 + 1][a4][a5] += dp[a1][a2][a3][a4][a5];
                            }
                            if (a4 < a[3] && a3 > a4 && a1 >= a2 && a2 >= a3) {
                                dp[a1][a2][a3][a4 + 1][a5] += dp[a1][a2][a3][a4][a5];
                            }
                            if (a5 < a[4] && a4 > a5 && a1 >= a2 && a2 >= a3 && a3 >= a4) {
                                dp[a1][a2][a3][a4][a5 + 1] += dp[a1][a2][a3][a4][a5];
                            }
                        }
                    }
                }
            }
        }
        printf("%lld\n", dp[a[0]][a[1]][a[2]][a[3]][a[4]]);
    }
    return 0;
}

 

Acwing272 最长公共上升子序列

熊大妈的奶牛在小沐沐的熏陶下开始研究信息题目。

小沐沐先让奶牛研究了最长上升子序列,再让他们研究了最长公共子序列,现在又让他们研究最长公共上升子序列了。

小沐沐说,对于两个数列A和B,如果它们都包含一段位置不一定连续的数,且数值是严格递增的,那么称这一段数是两个数列的公共上升子序列,而所有的公共上升子序列中最长的就是最长公共上升子序列了。

奶牛半懂不懂,小沐沐要你来告诉奶牛什么是最长公共上升子序列。

不过,只要告诉奶牛它的长度就可以了。

数列A和B的长度均不超过3000。

输入格式

第一行包含一个整数N,表示数列A,B的长度。

第二行包含N个整数,表示数列A。

第三行包含N个整数,表示数列B。

输出格式

输出一个整数,表示最长公共上升子序列的长度。

数据范围

1N30001≤N≤3000,序列中的数字均不超过2311231−1

输入样例:

4
2 2 1 3
2 1 2 3

输出样例:

2
#include
using namespace std;
const int N = 3e3 + 7;
int dp[N][N], a[N], b[N], n;
int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];
    for (int i = 1; i <= n; i++) cin >> b[i];
    for (int i = 1; i <= n; i++) {
        int val = 0;
        for (int j = 1; j <= n; j++) {
            if (a[i] == b[j]) dp[i][j] = val + 1;
            else dp[i][j] = dp[i - 1][j];
            if (b[j] < a[i]) val = max(val, dp[i - 1][j]);
        }
    }
    int ans = 0;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            ans = max(ans, dp[i][j]);
        }
    }
    printf("%d\n", ans);
    return 0;
}

 

POJ3666 Making the Grade

一条笔直的土路连接着FJ农场上的两个田地,但它改变的海拔比FJ想要的要多。他的牛不介意爬上或下一个斜坡,但他们不喜欢交替的山丘和山谷。FJ想要增加和清除道路上的泥土,这样它就变成了一个单调的斜坡(无论是向上倾斜还是向下倾斜)。 给你N个整数A1, ... , AN (1 ≤ N ≤ 2,000) 描述海拔(0 ≤ Ai ≤ 1,000,000,000) 在路上N个等距的位置,从第一个字段开始,到另一个字段结束。FJ想把这些高度调整成一个新的序列B1, . ... , BN 这要么是不增加的,要么是不减少的。由于增加或清除沿路任何位置的污物所需的费用相同,因此修路的总费用为 | A 1 - B 1| + | A 2 - B 2| + ... + | AN - BN | 请计算他的道路分级的最低费用,使它成为一个连续的斜坡。FJ高兴地告诉你,有符号的32位整数肯定可以用来计算答案。

Input

* Line 1: A single integer: N
* Lines 2..N+1: Line i+1 contains a single integer elevation: Ai

Output

* 第1行:一个整数,它是FJ对其土路进行分级的最小成本,因此它在高程上不会增加或减少。

Sample Input

7
1
3
2
4
5
3
9

Sample Output

3
#include
#include
#include
using namespace std;
const int N = 2e3 + 7;
const int INF = 0x3f3f3f3f;
int a[N], b[N], dp[N][N], n, m;
#define isDigit(ch) (ch >= '0' && ch <= '9')
#define raw(i) b[i]
#define val(x) lower_bound(b + 1, b + 1 + m, x) - b
inline int read() {
    int ans = 0;
    int f = 1;
    char ch = getchar();
    while (!isDigit(ch)) {
        if (ch == '-') f = -1;
        ch = getchar();
    }
    while (isDigit(ch)) ans = (ans << 3) + (ans << 1) + (ch ^ 48), ch = getchar();
    return ans * f;
}
int main() {
    memset(dp, 0x3f, sizeof(dp));
    n = read();
    for (int i = 1; i <= n; i++)
        a[i] = read(), b[i] = a[i];
    sort(b + 1, b + 1 + n);
    m = unique(b + 1, b + 1 + n) - b - 1;
    dp[0][0] = 0;
    for (int i = 1; i <= n; i++) {
        int val = dp[i - 1][0];
        for (int j = 1; j <= m; j++) {
            val = min(val, dp[i - 1][j]);
            dp[i][j] = min(dp[i][j], val + abs(a[i] - raw(j)));
        }
    }
    int ans = INF;
    for (int i = 1; i <= m; i++)
        ans = min(ans, dp[n][i]);
    printf("%d\n", ans);
    return 0;
}

 

Acwing274 移动服务

一个公司有三个移动服务员,最初分别在位置1,2,3处。

如果某个位置(用一个整数表示)有一个请求,那么公司必须指派某名员工赶到那个地方去。

某一时刻只有一个员工能移动,且不允许在同样的位置出现两个员工。

从 p 到 q 移动一个员工,需要花费 c(p,q)。

这个函数不一定对称,但保证 c(p,p)=0。

给出N个请求,请求发生的位置分别为 p1p1~pNpN。

公司必须按顺序依次满足所有请求,且过程中不能去其他额外的位置,目标是最小化公司花费,请你帮忙计算这个最小花费。

输入格式

第1行有两个整数L,N,其中L是位置数量,N是请求数量,每个位置从1到L编号。

第2至L+1行每行包含L个非负整数,第i+1行的第j个数表示c(i,j) ,并且它小于2000。

最后一行包含N个整数,是请求列表。

一开始三个服务员分别在位置1,2,3。

输出格式

输出一个整数M,表示最小花费。

数据范围

3L2003≤L≤200,
1N10001≤N≤1000

输入样例:

5 9
0 1 1 1 1
1 0 2 3 2
1 1 0 4 1
2 1 5 0 1
4 2 3 4 0
4 2 4 1 5 4 3 2 1

输出样例:

5

#include
using namespace std;
const int N = 1e3 + 7;
const int M = 2e2 + 7;
const int INF = 0x3f3f3f3f;
#define isdigit(ch) (ch >= '0' && ch <= '9')
int dp[N][M][M], c[M][M], p[N];
inline int read() {
    int ans = 0;
    int f = 1;
    char c = getchar();
    while (!isdigit(c)) {
        if (c == '-') f = -1;
        c = getchar();
    }
    while (isdigit(c)) ans = (ans << 3) + (ans << 1) + (c ^ 48), c = getchar();
    return ans * f;
}
int main() {
    memset(dp, 0x3f, sizeof(dp));
    int l = read(), n = read();
    dp[0][1][2] = 0;
    p[0] = 3;
    for (int i = 1; i <= l; i++) {
        for (int j = 1; j <= l; j++) {
            c[i][j] = read();
        }
    }
    for (int i = 1; i <= n; i++) {
        p[i] = read();
    }
    for (int i = 1; i <= n; i++) {
        for (int x = 1; x <= l; x++) {
            for (int y = 1; y <= l; y++) {
                if (x == y || x == p[i - 1] || y == p[i - 1]) continue;
                dp[i][x][y] = min(dp[i][x][y], dp[i - 1][x][y] + c[p[i - 1]][p[i]]);
                dp[i][p[i - 1]][y] = min(dp[i][p[i - 1]][y], dp[i - 1][x][y] + c[x][p[i]]);
                dp[i][x][p[i - 1]] = min(dp[i][x][p[i - 1]], dp[i - 1][x][y] + c[y][p[i]]);
            }
        }
    }
    int ans = INF;
    for (int x = 1; x <= l; x++) {
        for (int y = 1; y <= l; y++) {
            ans = min(ans, dp[n][x][y]);
        }
    }
    printf("%d\n", ans);
    return 0;
}

 

Acwing275 传纸条

 

小渊和小轩是好朋友也是同班同学,他们在一起总有谈不完的话题。

一次素质拓展活动中,班上同学安排坐成一个 mm 行 nn 列的矩阵,而小渊和小轩被安排在矩阵对角线的两端,因此,他们就无法直接交谈了。

幸运的是,他们可以通过传纸条来进行交流。

纸条要经由许多同学传到对方手里,小渊坐在矩阵的左上角,坐标(1,1),小轩坐在矩阵的右下角,坐标(m,n)。

从小渊传到小轩的纸条只可以向下或者向右传递,从小轩传给小渊的纸条只可以向上或者向左传递。 

在活动进行中,小渊希望给小轩传递一张纸条,同时希望小轩给他回复。

班里每个同学都可以帮他们传递,但只会帮他们一次,也就是说如果此人在小渊递给小轩纸条的时候帮忙,那么在小轩递给小渊的时候就不会再帮忙,反之亦然。 

还有一件事情需要注意,全班每个同学愿意帮忙的好感度有高有低(注意:小渊和小轩的好心程度没有定义,输入时用0表示),可以用一个0-100的自然数来表示,数越大表示越好心。

小渊和小轩希望尽可能找好心程度高的同学来帮忙传纸条,即找到来回两条传递路径,使得这两条路径上同学的好心程度之和最大。

现在,请你帮助小渊和小轩找到这样的两条路径。

输入格式

第一行有2个用空格隔开的整数 mm 和 nn,表示学生矩阵有 mm 行 nn 列。

接下来的 mm 行是一个 mnm∗n 的矩阵,矩阵中第 ii 行 jj 列的整数表示坐在第 ii 行 jj 列的学生的好心程度,每行的 nn 个整数之间用空格隔开。

输出格式

输出一个整数,表示来回两条路上参与传递纸条的学生的好心程度之和的最大值。

数据范围

1n,m501≤n,m≤50

输入样例:

3 3
0 3 9
2 8 5
5 7 0

输出样例:

34

#include
using namespace std;
const int N = 1e2 + 7;
#define isdigit(ch) (ch >= '0' && ch <= '9')
int dp[N][N][N], a[N][N], n, m;
inline int read() {
    int ans = 0;
    int f = 1;
    char c = getchar();
    while (!isdigit(c)) {
        if (c == '-') f = -1;
        c = getchar();
    }
    while (isdigit(c)) ans = (ans << 3) + (ans << 1) + (c ^ 48), c = getchar();
    return ans * f;
}
int main() {
    n = read(), m = read();
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            a[i][j] = read();
        }
    }
    for (int i = 2; i <= m + n; i++) {
        for (int x1 = 1; x1 <= n; x1++) {
            for (int x2 = 1; x2 <= n; x2++) {
                int y1 = i - x1;
                int y2 = i - x2;
                int w = a[x1][y1];
                if (x1 != x2) w += a[x2][y2];
                dp[i][x1][x2] = max(dp[i][x1][x2], dp[i - 1][x1][x2] + w);
                dp[i][x1][x2] = max(dp[i][x1][x2], dp[i - 1][x1][x2 - 1] + w);
                dp[i][x1][x2] = max(dp[i][x1][x2], dp[i - 1][x1 - 1][x2] + w);
                dp[i][x1][x2] = max(dp[i][x1][x2], dp[i - 1][x1 - 1][x2 - 1] + w);
            }
        }
    }
    printf("%d\n", dp[n + m][n][n]);
    return 0;
}

 

 Gym - 100340A Cookies

圣诞老人共有M个饼干,准备全部分给N个孩子。

每个孩子有一个贪婪度,第 i 个孩子的贪婪度为 g[i]。

如果有 a[i] 个孩子拿到的饼干数比第 i 个孩子多,那么第 i 个孩子会产生 g[i]*a[i]的怨气。

给定N、M和序列g,圣诞老人请你帮他安排一种分配方式,使得每个孩子至少分到一块饼干,并且所有孩子的怨气总和最小。

输入格式

第一行包含两个整数N,M。

第二行包含N个整数表示g1g1~gNgN。

输出格式

第一行一个整数表示最小怨气总和。

第二行N个空格隔开的整数表示每个孩子分到的饼干数,若有多种方案,输出任意一种均可。

数据范围

1N301≤N≤30,
NM5000N≤M≤5000,
1gi1071≤gi≤107

输入样例:

3 20
1 2 3

输出样例:

2
2 9 9

#include
using namespace std;
const int N = 37;
const int M = 5e3 + 7;
struct Node {
    int g;
    int id;
    friend bool operator<(Node a, Node b) {
        return a.g > b.g;
    }
}a[N];
int n, m, dp[N][M], sum[N], ans[N];
int main() {
    freopen("cookies.in", "r", stdin);
    freopen("cookies.out", "w", stdout);
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &a[i].g);
        a[i].id = i;
    }
    sort(a + 1, a + 1 + n);
    for (int i = 1; i <= n; i++) {
        sum[i] = sum[i - 1] + a[i].g;
    }
    memset(dp, 0x3f, sizeof(dp));
    dp[0][0] = 0;
    for (int i = 1; i <= n; i++) {
        for (int j = 0; j <= m; j++) {
            int res = 0x3f3f3f3f;
            for (int k = 0; k < i; k++) {
                if (j < i - k) continue;
                int temp = dp[k][j - (i - k)] + k * (sum[i] - sum[k]);
                res = min(res, temp);
            }
            if (j >= i) res = min(dp[i][j - i], res);
            dp[i][j] = res;
        }
    }
    printf("%d\n", dp[n][m]);
    int i = n, j = m, h = 0;
    while (i && j) {
        if (j >= i && dp[i][j] == dp[i][j - i]) j -= i, h++;
        else {
            for (int k = 1; k <= min(i, j); k++) {
                if (dp[i][j] == dp[i - k][j - k] + (sum[i] - sum[i - k]) * (i - k)) {
                    for (int u = i; u > i - k; u--) {
                        ans[a[u].id] = h + 1;
                    }
                    i -= k, j -= k;
                    break;
                }
            }
        }
    }
    for (int k = 1; k <= n; k++)
        printf("%d%c", ans[k], k == n ? '\n' : ' ');
    return 0;
}

 

你可能感兴趣的:(动态规划专题之线性dp)