简单 dp

1.摆花问题

题目描述
小明的花店新开张,为了吸引顾客,他想在花店的门口摆上一排花,共m盆。通过调查顾客的喜好,小明列出了顾客最喜欢的n种花,从1到n标号。为了在门口展出更多种花,规定第i种花不能超过ai盆,摆花时同一种花放在一起,且不同种类的花需按标号的从小到大的顺序依次摆列。
试编程计算,一共有多少种不同的摆花方案。

输入
输入共2行。
第一行包含两个正整数n和m,中间用一个空格隔开。
第二行有n个整数,每两个整数之间用一个空格隔开,依次表示a1、a2、……an。

输出
输出只有一行,一个整数,表示有多少种方案。注意:因为方案数可能很多,请输出方案数对1000007取模的结果。

样例输入
2 4
3 2
样例输出
2
提示

样例说明:


有2种摆花的方案,分别是(1,1,1,2),(1,1,2,2)。括号里的1和2表示两种花,比如第一个方案是前三个位置摆第一种花,第四个位置摆第二种花。

对于20%数据,有0<n≤8,0<m≤8,0≤ai≤8;

对于50%数据,有0<n≤20,0<m≤20,0≤ai≤20;

对于100%数据,有0<n≤100,0<m≤100,0≤ai≤100。

代码:

#include <bits/stdc++.h>
using namespace std;

const int mod = 1000007;
int N, M;
int a[110];
int dp[110][110];

int main() {
    scanf("%d%d", &N, &M);
    for(int i = 1; i <= N; i ++)
        scanf("%d", &a[i]);

    // 不管放多少盆花 如果只有一种那么方案书一定是 1
    for(int i = 0; i <= a[1]; i ++)
        dp[1][i] = 1;

    for(int i = 2; i <= N; i ++) {
        for(int j = 0; j <= M; j ++) {
            for(int k = 0;k <= min(a[i], j); k ++)
                dp[i][j] = (dp[i][j] + dp[i - 1][j - k]) % mod;
        }
    }

    printf("%d\n", dp[N][M]);
    return 0;
}
View Code

简单 dp  dp[i][j] 表示摆了前 i 种花一共摆了 j 盆的方案数

2.合唱队问题

描述   

N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学排成合唱队形。 合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1,2…,K,他们的身高分别为T1,T2,…,TK, 则他们的身高满足T1<...<Ti>Ti+1>…>TK(1<=i<=K)。 你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。

输入格式

输入的第一行是一个整数N(2<=N<=100),表示同学的总数。第一行有n个整数,用空格分隔,第i个整数Ti(130<=Ti<=230)是第i位同学的身高(厘米)。

输出格式

输出包括一行,这一行只包含一个整数,就是最少需要几位同学出列。

样例输入

8

186 186 150 200 160 130 197 220

样例输出

4

代码:

#include <bits/stdc++.h>
using namespace std;

int N;
int a[1010], b[1010];
int l[1010], r[1010];

int main() {
    scanf("%d", &N);
    for(int i = 0; i < N; i ++) {
        scanf("%d", &a[i]);
        b[i] = a[i];
    }

    for(int i = 0; i <= N / 2; i ++)
        swap(a[i], a[N - 1 - i]);

    for(int i = 0; i < N; i ++) {
        for(int j = 0; j < i; j ++)
            if(b[i] > b[j])
                l[i] = max(l[i], l[j] + 1);
    }

    for(int i = 0; i < N; i ++) {
        for(int j = 0; j < i; j ++)
            if(a[i] > a[j])
                r[i] = max(r[i], r[j] + 1);
    }

    int ans = 0;
    for(int i = 0; i < N; i ++)
        ans = max(l[i] + r[i] - 1, ans);

    printf("%d\n", N - ans);
    return 0;
}
View Code

正向反向求最长严格上升子序列枚举中间点 i 因为 i 被算了两次是重复的所以要减去 1

3.方格取数

题目描述

设有N×N的方格图(N≤9),我们将其中的某些方格中填入正整数,而其他的方格中则放入数字0。
某人从图的左上角的AA点出发,可以向下行走,也可以向右走,直到到达右下角的BB点。在走过的路上,他可以取走方格中的数(取走后的方格中将变为数字0)。
此人从(0, 0)点到(n,n)点共走两次,试找出2条这样的路径,使得取得的数之和为最大。

输入:

8
6 1 7
3 2 9
2 3 5
1 4 3
3 4 3
6 6 6
1 7 1
0 0 0

输出:
67

代码:

#include <bits/stdc++.h>
using namespace std;

int N;
int mp[20][20];
int dp[20][20][20][20];

int main() {
    scanf("%d", &N);
    int x, y, z;
    while(~scanf("%d%d%d", &x, &y, &z)) {
        if(!x && !y && !z) break;
        mp[x][y] = z;
    }

    for(int i = 1; i <= N; i ++)
        for(int j = 1; j <= N; j ++)
            for(int k = 1; k <= N; k ++)
                for(int l = 1; l <= N; l ++) {
                    dp[i][j][k][l] = max(max(dp[i - 1][j][k - 1][l], dp[i][j - 1][k][l - 1]),
                                         max(dp[i - 1][j][k][l - 1],dp[i][j - 1][k - 1][l]))+mp[i][j];

                    if(i != k || j != l) dp[i][j][k][l] += mp[k][l];
                }

    printf("%d\n", dp[N][N][N][N]);
    return 0;
}
View Code

两个同时走 四维 dp 时间复杂度 $O(N^4)$  还有一个时间复杂度为 $O(N^3)$  的写法

4.乘积最大

题目描述:

设有一个长度为N的数字串,要求选手使用K个乘号将它分成K+1个部分,找出一种分法,使得这K+1个部分的乘积最大。例如,数字串为312,当N=3,K=1时会有以下两种分法:3*12=36和31*2=62,符合题目要求的结果为31*2=62。程序的输入共有两行:第一行共有两个自然数N、K(6<=N<=40,1<=K<=6),第二行是一个长度为N的数字串,输出一个整数,表示求得的最大乘积。

输入格式
第 1 行为整数 n,n≤50。
第 2 行为整数 k,k≤10。
第 3 行为数字字符串。
输出格式
一行一个数,表示最大的乘积。
输入样例
6
3
310143
输出样例
3720

代码:

#include <bits/stdc++.h>
using namespace std;

int N, K;
string s;
int dp[55][15];
int num[55][55];

int main() {
    scanf("%d%d", &N, &K);
    cin >>s;
    
    memset(dp, 0, sizeof(dp));
    for(int i = 0; i < N; i ++) {
        int sum = 0;
        for(int j = i; j < N; j ++) {
            sum = sum * 10 + (s[j] - '0');
            num[i][j] = sum;
        }
    }

    for(int i = 0; i < N; i ++)
        dp[i][0] = num[0][i];

    for(int i = 0; i < N; i ++)
        for(int j = 1; j <= K; j ++)
            for(int k = 0; k < i; k ++)
                dp[i][j] = max(dp[k][j - 1] * num[k + 1][i], dp[i][j]);

    printf("%d\n", dp[N - 1][K]);
    return 0;
}
View Code

dp[i][j] 代表的是在 s[0] 到 s[i] 的字符串中插入 j 个乘号最大乘积 num[i][j] 代表 以 s[i] 开始以 s[j] 结尾的数字大小 状态转移方程为 $dp[i][j] = max(dp[k][j - 1] * num[k + 1][i], dp[i][j])$

5.书的抄写

Description

有M本书(编号为1,2,…,M),每本书都有一个页数(分别是P1,P2,…,PM)。想将每本都复制一份。将这M本书分给K个抄写员(1<=K<=M<=500),每本书只能分配给一个抄写员进行复制。每个抄写员至少被分配到一本书,而且被分配到的书必须是连续顺序的。复制工作是同时开始进行的,并且每个抄写员复制一页书的速度都是一样的。所以,复制完所有书稿所需时间取决于分配得到最多工作的那个抄写员的复制时间。试找一个最优分配方案,使分配给每一个抄写员的页数的最大值尽可能小。 

Input

第一行两个整数M、K;(K<=M<=500) 
第二行M个整数,第i个整数表示第i本书的页数。

Output

1.输出为一个数,即分配给每一个抄写员的页数的最大值

 2.共 K 行,每行 2 个正整数,第 i 行表示第 i 个人抄写的书的起始编号和终止编号,每两个数之间用一个空格隔开。K 行的起始编号应该从小到大排列,如果有多解,则尽可能让前面的人少抄写

Sample Input

9 3
1 2 3 4 5 6 7 8 9


Sample Output

1.

17

 

2.

1-5 (15页) 
6-7 (13页) 
8-9 (17页)

代码:

#include <bits/stdc++.h>
using namespace std;

#define inf 0x3f3f3f3f
int N, K;
int p[510], sum[510];
int dp[510][510];

int main() {
    scanf("%d%d", &N, &K);

    memset(sum, 0, sizeof(sum));
    for(int i = 1; i <= N; i ++) {
        scanf("%d", &p[i]);
        sum[i] = sum[i - 1] + p[i];
    }

    memset(dp, inf, sizeof(dp));
    for(int i = 1; i <= K; i ++) {
        for(int j = i; j <= N; j ++) {
            if(i == 1) dp[i][j] = sum[j];
            else {
                for(int k = i - 1; k <= j - 1; k ++)
                    dp[i][j] = min(max(dp[i - 1][k], sum[j] - sum[k]), dp[i][j]);
            }
        }
    }

    printf("%d\n", dp[K][N]);
    return 0;
}
View Code

dp[i][j] 代表 i 个人 抄写 j 本书耗时最短 代码是输出 1 的结果 输出 2 不会 :-(      

被 dp 折磨的一上午 开发智力???想豁奶茶了

posted @ 2019-01-21 10:45  丧心病狂工科女  阅读(447)  评论(0编辑  收藏  举报