北大2018acm暑期课二动态规划

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_40379678/article/details/81260181

每次写动态规划都是百度百度再百度,Orz。

股票买卖

描述

最近越来越多的人都投身股市,阿福也有点心动了。谨记着“股市有风险,入市需谨慎”,阿福决定先来研究一下简化版的股票买卖问题。

假设阿福已经准确预测出了某只股票在未来 N 天的价格,他希望买卖两次,使得获得的利润最高。为了计算简单起见,利润的计算方式为卖出的价格减去买入的价格。

同一天可以进行多次买卖。但是在第一次买入之后,必须要先卖出,然后才可以第二次买入。

现在,阿福想知道他最多可以获得多少利润。

输入

输入的第一行是一个整数 T (T <= 50) ,表示一共有 T 组数据。
接下来的每组数据,第一行是一个整数 N (1 <= N <= 100, 000) ,表示一共有 N 天。第二行是 N 个被空格分开的整数,表示每天该股票的价格。该股票每天的价格的绝对值均不会超过 1,000,000 。

输出

对于每组数据,输出一行。该行包含一个整数,表示阿福能够获得的最大的利润。

样例输入

3
7
5 14 -2 4 9 3 17
6
6 8 7 4 1 -2
4
18 9 5 2

样例输出

28
2
0

提示

对于第一组样例,阿福可以第 1 次在第 1 天买入(价格为 5 ),然后在第 2 天卖出(价格为 14 )。第 2 次在第 3 天买入(价格为 -2 ),然后在第 7 天卖出(价格为 17 )。一共获得的利润是 (14 - 5) + (17 - (-2)) = 28
对于第二组样例,阿福可以第 1 次在第 1 天买入(价格为 6 ),然后在第 2 天卖出(价格为 8 )。第 2 次仍然在第 2 天买入,然后在第 2 天卖出。一共获得的利润是 8 - 6 = 2
对于第三组样例,由于价格一直在下跌,阿福可以随便选择一天买入之后迅速卖出。获得的最大利润为 0

如果单纯定义状态dp【i】为前i天赚的钱,那么明显不满足无后效性。这题两次买卖会把问题分为两个子问题——在0~i和i~n这两个区间里分别一买一卖最大能赚多少?而在这个子问题可以在o(n)复杂度完成。(从左往右枚举分割点i, 记录左边最小的价格minvalue,则当前左区间最大收益为left [ i ] = max(left [ i ] , a[ i ] - minvalue),right[ i ]计算方法相同 )。

#include <queue>
#include <cstdio>
#include <cstring>
#include <utility>
#include <iostream>
#include <map>
#include <algorithm>
#include <cmath>
#include <string>
#include <vector>
using namespace std;
typedef pair<int, int> P;
typedef long long ll;
#define N 100010
#define M 3645
const int INF = 0x3f3f3f3f;
const double eps = 1e-5;
const double PI = acos(-1);
#define fi first
#define se second
#define rep(i, lll, nnn) for(int i = (lll); i <= (nnn); i++)

int n, a[N], le[N], rt[N];

int main()
{
    //freopen("data.txt", "r", stdin);

    int t;
    scanf("%d", &t);
    while(t--) {
        scanf("%d", &n);
        rep(i, 1, n) scanf("%d", &a[i]);
        int mn = INF;
        memset(le, 0, sizeof le);
        rep(i, 1, n) {
            mn = min(mn, a[i]);
            le[i] = max(a[i] - mn, le[i - 1]);
        }
        int mx = -INF;
        memset(rt, 0, sizeof rt);
        for(int i = n; i >= 0; i--) {
            mx = max(a[i], mx);
            rt[i] = max(rt[i + 1], mx - a[i]);
            //cout << "mx  " << mx << endl;
        }
        int ans = 0;
        //rep(i, 1, n) cout << le[i] <<' ' << rt[i] << endl;
        rep(i, 1, n) ans = max(le[i] + rt[i], ans);
        printf("%d\n", ans);
    }

    return 0;
}

课程大作业

描述

小明是北京大学信息科学技术学院三年级本科生。他喜欢参加各式各样的校园社团。这个学期就要结束了,每个课程大作业的截止时间也快到了,可是小明还没有开始做。每一门课程都有一个课程大作业,每个课程大作业都有截止时间。如果提交时间超过截止时间X天,那么他将会被扣掉X分。对于每个大作业,小明要花费一天或者若干天来完成。他不能同时做多个大作业,只有他完成了当前的项目,才可以开始一个新的项目。小明希望你可以帮助他规划出一个最好的办法(完成大作业的顺序)来减少扣分。

输入

输入包含若干测试样例。
输入的第一行是一个正整数T,代表测试样例数目。
对于每组测试样例,第一行为正整数N(1 <= N <= 15)代表课程数目。
接下来N行,每行包含一个字符串S(不多于50个字符)代表课程名称和两个整数D(代表大作业截止时间)和C(完成该大作业需要的时间)。
注意所有的课程在输入中出现的顺序按照字典序排列。

输出

对于每组测试样例,请输出最小的扣分以及相应的课程完成的顺序。
如果最优方案有多个,请输出字典序靠前的方案。

样例输入

2 
3 
Computer 3 3 
English 20 1 
Math 3 2 
3
Computer 3 3 
English 6 3 
Math 6 3

样例输出

2 
Computer 
Math 
English 
3 
Computer 
English 
Math

提示

第二个测试样例, 课程完成顺序Computer->English->Math 和 Computer->Math->English 都会造成3分罚分, 但是我们选择前者,因为在字典序中靠前.

看这题就想难道不是搜索???这也能dp???然后老师讲这种N为15的小数据的就可能是状压dp。每次只做一门课,所以二进制状压后每个state由比它二进制位少一个1的状态转移过来。第二是要记录路径并且最小字典序,那么就要在dp过程中根据各种条件记录下上一个状态。

对我这种小白真是太不友好了。

#include <queue>
#include <cstdio>
#include <cstring>
#include <utility>
#include <iostream>
#include <map>
#include <algorithm>
#include <cmath>
#include <string>
#include <vector>
using namespace std;
typedef pair<int, int> P;
typedef long long ll;
#define N 100010
#define M 3645
const int INF = 0x3f3f3f3f;
const double eps = 1e-5;
const double PI = acos(-1);
#define fi first
#define se second
#define rep(i, lll, nnn) for(int i = (lll); i <= (nnn); i++)

int n, t;
struct Homework {
   string name;
   int d, c;
}hw[20];
struct Node {
   int pre;///上一个状态
   int minScore;///最小减去的分
   int last;///由上一个状态转移到这个状态做的哪一门作业
   int finishDay;///当前状态的结束时间
}dp[(1 << 16) + 20];

vector<int> GetPath(int status)
{
    vector<int> path;
    while(status) {
        path.push_back(dp[status].last);
        status = dp[status].pre;
    }
    reverse(path.begin(), path.end());
    return path;
}

int main()
{
   // freopen("data.txt", "r", stdin);

    int t;
    scanf("%d", &t);
    while(t--) {
        scanf("%d", &n);
        rep(i, 0, n - 1)
            cin >> hw[i].name >> hw[i].d >> hw[i].c;
        dp[0].finishDay = 0;
        dp[0].minScore = 0;
        dp[0].pre = -1;
        int m = 1 << n;
        rep(i, 1, m - 1) {///枚举所有状态,按顺序
            dp[i].minScore = 1 << 30;
            rep(j, 0, n - 1) if(i & (1 << j)){
                ///枚举每个状态的前一个状态,“人人为我”
                int pre = i - (1 << j);
                int finishDay = dp[pre].finishDay + hw[j].c;
                int tmpScore = finishDay - hw[j].d;
                if(tmpScore < 0) tmpScore = 0;
                if( dp[i].minScore > dp[pre].minScore + tmpScore) {
                    dp[i].minScore = dp[pre].minScore + tmpScore;
                    dp[i].pre = pre;
                    dp[i].finishDay = finishDay;
                    dp[i].last = j;
                }

                if( dp[i].minScore == dp[pre].minScore + tmpScore) {
                    vector<int> p1 = GetPath(dp[i].pre);
                    vector<int> p2 = GetPath(pre);
                    if(p2 < p1) {
                        dp[i].pre = pre;
                        dp[i].finishDay = finishDay;
                        dp[i].last = j;
                    }
                }
            }
        }

        printf("%d\n", dp[m - 1].minScore);
        vector<int> path = GetPath(m - 1);
        for( int i = 0; i < path.size(); ++i)
            cout << hw[path[i]].name << endl;
    }

    return 0;
}

最佳加法表达式

描述

给定n个1到9的数字,要求在数字之间摆放m个加号(加号两边必须有数字),使得所得到的加法表达式的值最小,并输出该值。例如,在1234中摆放1个加号,最好的摆法就是12+34,和为36

输入

有不超过15组数据
每组数据两行。第一行是整数m,表示有m个加号要放( 0<=m<=50)
第二行是若干个数字。数字总数n不超过50,且 m <= n-1

输出

对每组数据,输出最小加法表达式的值

样例输入

2
123456
1
123456
4
12345

样例输出

102
579
15

提示

要用到高精度计算,即用数组来存放long long 都装不下的大整数,并用模拟列竖式的办法进行大整数的加法。

很明显的两个限制因素——加号个数和数字个数。

定义状态dp[i][j]为前i个数字放j个加号的从1~i的最小值,则状态的转移方程:dp[i][j] = min ( dp[k][ j - 1] + num(k + 1, j) );意思是最后一个加号在k后面。

高精度什么的也要注意一下, string大法好。

#include <queue>
#include <cstdio>
#include <cstring>
#include <utility>
#include <iostream>
#include <map>
#include <algorithm>
#include <cmath>
#include <string>
#include <vector>
using namespace std;
typedef pair<int, int> P;
typedef long long ll;
#define N 55
#define M 3645
//const int INF = 0x3f3f3f3f;
const double eps = 1e-5;
const double PI = acos(-1);
#define fi first
#define se second
#define rep(i, lll, nnn) for(int i = (lll); i <= (nnn); i++)

string add(const string & A, const string & B)
{
    int la = A.length(), lb = B.length(), lc, i, x;//x 是上一位进的
    int a[N] = {0}, b[N] = {0}, c[N] = {0};

    for( i = 0; i < la; i++)
        a[la - i - 1] = A[i] - '0';

    for( i = 0; i < lb; i++)
        b[lb - i - 1] = B[i] - '0';

    lc = 0;
    x = 0;
    while( lc <= la || lc <= lb ){
        c[lc] = a[lc] + b[lc] + x;
        x = c[lc] / 10;
        c[lc] %= 10;
        lc++;
    }
    c[lc] = x;

    while( c[lc] == 0 )
        lc--;

    string ans = "";
    for(i = lc; i >= 0; i--) ans += char(c[i] + '0');

    return ans;
}

string Min(const string & A, const string & B)
{
    if(A.length() < B.length()) return A;
    if(A.length() > B.length()) return B;
    for(int i = 0; i < A.length(); i++)
        if(A[i] < B[i]) return A;
        else if(A[i] > B[i]) return B;
    return A;
}

string num[N][N], dp[N][N], s;
int m, n;
const string INF = "9999999999999999999999999999999999999999";
///dp[i][j] = min(dp[i][j], dp[i - 1][k] + val(k+1, j));
int main()
{
   // freopen("data.txt", "r", stdin);

    while(cin >> m >> s) {
        n = s.length();
        rep(i, 0, m) rep(j, 0, n) dp[i][j] = INF;
        rep(i, 1, n) rep(j, i, n) num[i][j] = s.substr(i - 1, j - i + 1);
        rep(i, 1, n) dp[0][i] = num[1][i];
      //  cout << dp[0][n] << endl;

        rep(i, 1, m) rep(j, i + 1, n) rep(k, i, j - 1) {
            dp[i][j] = Min(dp[i][j], add(dp[i - 1][k], num[k + 1][j]));
            //cout << dp[i][j] << ' ' <<i << ' ' <<j << endl;
        }
        cout << dp[m][n] << endl;
    }

    return 0;
}

Zipper

描述

Given three strings, you are to determine whether the third string can be formed by combining the characters in the first two strings. The first two strings can be mixed arbitrarily, but each must stay in its original order.

For example, consider forming "tcraete" from "cat" and "tree":

String A: cat
String B: tree
String C: tcraete

As you can see, we can form the third string by alternating characters from the two strings. As a second example, consider forming "catrtee" from "cat" and "tree":

String A: cat
String B: tree
String C: catrtee

Finally, notice that it is impossible to form "cttaree" from "cat" and "tree".

输入

The first line of input contains a single positive integer from 1 through 1000. It represents the number of data sets to follow. The processing for each data set is identical. The data sets appear on the following lines, one data set per line.

For each data set, the line of input consists of three strings, separated by a single space. All strings are composed of upper and lower case letters only. The length of the third string is always the sum of the lengths of the first two strings. The first two strings will have lengths between 1 and 200 characters, inclusive.

输出

For each data set, print:

Data set n: yes

if the third string can be formed from the first two, or

Data set n: no

if it cannot. Of course n should be replaced by the data set number. See the sample output below for an example.

样例输入

3
cat tree tcraete
cat tree catrtee
cat tree cttaree

样例输出

Data set 1: yes
Data set 2: yes
Data set 3: no

那个什么最长公共子序列的状态定义是dp[i][j]表示序列a从0到i和序列b从0到j的最长公共子序列。我就想这题定义状态dp[i][j]

为序列a从1到i和序列b从1到j是否可以组成c序列的前i+j个。状态转移方程为dp[i][j] = {true, if dp[i - 1][j] == true && a[i] == c[i + j] or dp[i][j - 1] && b[j] == c[i  + j]}。这题还要注意dp的初始状态的定义,因为dp[0][j]或者dp[i][0]是无法从前面的状态转移过来的,所以这些要提前处理。

#include <queue>
#include <cstdio>
#include <cstring>
#include <utility>
#include <iostream>
#include <map>
#include <algorithm>
#include <cmath>
#include <string>
#include <vector>
using namespace std;
typedef pair<int, int> P;
typedef long long ll;
#define N 210
#define M 3645
const int INF = 0x3f3f3f3f;
const double eps = 1e-5;
const double PI = acos(-1);
#define fi first
#define se second
#define rep(i, lll, nnn) for(int i = (lll); i <= (nnn); i++)

char a[N], b[N], c[N * 2];
int i, j, k;
int la, lb, lc;
bool dp[N][N];

int main()
{
   // freopen("data.txt", "r", stdin);

    int t;
    scanf("%d", &t);
    rep(Set, 1, t) {
        scanf("%s%s%s", a + 1, b + 1, c + 1);
        la = strlen(a + 1); lb = strlen(b + 1); lc = strlen(c + 1);
        if(la + lb != lc) {
            printf("Data set %d: no\n", Set);
            continue;
        }

        memset(dp, 0, sizeof dp);
        dp[0][0] = true;

        rep(i, 1, la) if(a[i] == c[i]) dp[i][0] = true; else break;
        rep(i, 1, lb) if(b[i] == c[i]) dp[0][i] = true; else break;
        rep(i, 1, la) rep(j, 1, lb) {
            if(a[i] == c[i + j] && dp[i - 1][j]) dp[i][j] = true;
            if(b[j] == c[i + j] && dp[i][j - 1]) dp[i][j] = true;
            //dp[i][j] = dp[i - 1][j] || dp[i][j - 1];
        }

        printf("Data set %d: ", Set);
        printf(dp[la][lb]?"yes\n":"no\n");
    }

    return 0;
}

切割回文

描述

阿福最近对回文串产生了非常浓厚的兴趣。

如果一个字符串从左往右看和从右往左看完全相同的话,那么就认为这个串是一个回文串。例如,“abcaacba”是一个回文串,“abcaaba”则不是一个回文串。

阿福现在强迫症发作,看到什么字符串都想要把它变成回文的。阿福可以通过切割字符串,使得切割完之后得到的子串都是回文的。

现在阿福想知道他最少切割多少次就可以达到目的。例如,对于字符串“abaacca”,最少切割一次,就可以得到“aba”和“acca”这两个回文子串。

输入

输入的第一行是一个整数 T (T <= 20) ,表示一共有 T 组数据。
接下来的 T 行,每一行都包含了一个长度不超过的 1000 的字符串,且字符串只包含了小写字母。

输出

对于每组数据,输出一行。该行包含一个整数,表示阿福最少切割的次数,使得切割完得到的子串都是回文的。

样例输入

3
abaacca
abcd
abcba

样例输出

1
3
0

提示

对于第一组样例,阿福最少切割 1 次,将原串切割为“aba”和“acca”两个回文子串。
对于第二组样例,阿福最少切割 3 次,将原串切割为“a”、“b”、“c”、“d”这四个回文子串。
对于第三组样例,阿福不需要切割,原串本身就是一个回文串。

这种题我做过,bc小姐姐出的校赛题的dp就是这种题目加路径记录。首先要预处理出s[i]到s[j]是否是回文串,记得去年我看紫书刘汝佳写了个o(n^3)的预处理,并且说有o(n ^ 2)的写法,当时觉得真神奇还能n^2,现在我自己就能写出o(n ^ 2)了,也是递推的思想。然后定义状态dp[i]是1~i的回文串个数,如果是s[i]s到s[j]是回文串,那么dp[i] = min(dp[i], dp[j - 1] + 1);

#include <queue>
#include <cstdio>
#include <cstring>
#include <utility>
#include <iostream>
#include <map>
#include <algorithm>
#include <cmath>
#include <string>
#include <vector>
using namespace std;
typedef pair<int, int> P;
typedef long long ll;
#define N 1010
#define M 3645
const int INF = 0x3f3f3f3f;
const double eps = 1e-5;
const double PI = acos(-1);
#define fi first
#define se second
#define rep(i, lll, nnn) for(int i = (lll); i <= (nnn); i++)

char s[N];
int dp[N], ls;
bool ok[N][N];
//
//dp[i][j] =
//if(i == j) 0;
//if(i + 1 == j) if(a[i] == a[j]) 1 else 0
//if(a[i] == a[j]) dp[i + 1][j - 1] else min(dp[i + 1][j], dp[i][j - 1]) = 1
int main()
{
    freopen("data.txt", "r", stdin);

    int t;
    scanf("%d", &t);
    while(t--) {
        scanf("%s", s + 1);
        ls = strlen(s + 1);
        memset(ok, false, sizeof ok);

        rep(i, 1, ls) ok[i][i] = true;
        for(int i = ls - 1; i >= 1; i--) rep(j, i + 1, ls) {
            if(j == i + 1) {
                if(s[i] == s[j]) ok[i][j] = true;
            }
            else if(s[j] == s[i] && ok[i + 1][j - 1]) ok[i][j] = true;
        }
//rep(i, 1, ls) rep(j, 1, i) if(ok[j][i]) cout <<j << ' ' << i << endl;
        memset(dp, INF, sizeof dp);
        dp[0] = 0;
        rep(i, 1, ls) rep(j, 1, i) if(ok[j][i]) dp[i] = min(dp[i], dp[j - 1] + 1);

        printf("%d\n", dp[ls] - 1);
    }

    return 0;
}

:炮兵阵地

描述

司令部的将军们打算在N*M的网格地图上部署他们的炮兵部队。一个N*M的地图由N行M列组成,地图的每一格可能是山地(用"H" 表示),也可能是平原(用"P"表示),如下图。在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队);一支炮兵部队在地图上的攻击范围如图中黑色区域所示:


如果在地图中的灰色所标识的平原上部署一支炮兵部队,则图中的黑色的网格表示它能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。图上其它白色网格均攻击不到。从图上可见炮兵的攻击范围不受地形的影响。
现在,将军们规划如何部署炮兵部队,在防止误伤的前提下(保证任何两支炮兵部队之间不能互相攻击,即任何一支炮兵部队都不在其他支炮兵部队的攻击范围内),在整个地图区域内最多能够摆放多少我军的炮兵部队。

输入

第一行包含两个由空格分割开的正整数,分别表示N和M;
接下来的N行,每一行含有连续的M个字符('P'或者'H'),中间没有空格。按顺序表示地图中每一行的数据。N <= 100;M <= 10。

输出

仅一行,包含一个整数K,表示最多能摆放的炮兵部队的数量。

样例输入

5 4
PHPP
PPHH
PPPP
PHPP
PHHP

样例输出

6

这题信息量有点大。。。Orz。T-T.

1.状态的定义:N很大M很小,且如果按照行数递增的顺序看当前行会受到前两行影响(后面的行还没放,且前两行之前的行不会对当前状态造成影响,所以有无后效性),所以把行定义为状态挺合适。十个位置int就能存。dp[i][j][k] 表示第i行状态为k,前一行状态为j的最值。但是2^10是挺大的数字,存不下啊……其实某个位置放炮兵了它左俩和右俩的位置就不可能放了,这就剔除了许多无用的状态,把可能的状态哈希到成123456的排列里其实只有60+个。

2.状态的转移:按顺序枚举jkl表示第i-1、i、i+1行的状态,如果三个状态兼容不冲突就进行状态转移。

3.代码的实现:重点是位运算判断状态之间是否冲突。直接&运算就行了,如果同一列都有炮兵那么与运算就不为0。有点秀。

#include <queue>
#include <cstdio>
#include <cstring>
#include <utility>
#include <iostream>
#include <map>
#include <algorithm>
#include <cmath>
#include <string>
#include <vector>
using namespace std;
typedef pair<int, int> P;
typedef long long ll;
#define N 110
#define M 3645
const int INF = 0x3f3f3f3f;
const double eps = 1e-5;
const double PI = acos(-1);
#define fi first
#define se second
#define rep(i, lll, nnn) for(int i = (lll); i <= (nnn); i++)

int n, m, tot, res;
int a[N];
int st[65];
int sum[65];
int dp[N][65][65];
int dat[65];

int get(int x)
{
    int ans = 0;
    while(x) ans += (x&1), x >>= 1;
    return ans;
}

int main()
{
    //freopen("data.txt", "r", stdin);

    scanf("%d%d", &n, &m);
    rep(i, 1, n) rep(j, 0, m - 1) {
        char ch = getchar();
        while(ch != 'P' && ch != 'H') ch = getchar();
        if(ch == 'H') dat[i] |= (1 << j);
    }

    int mm = (1 << m) - 1;
    rep(i, 0, mm) if(!(i & (i << 1)) && !(i & (i << 2))) {
        st[++tot] = i; sum[tot] = get(i);
    }

    rep(i, 1, tot) if(!(st[i] & dat[1])) dp[1][0][i] = sum[i];
    rep(i, 1, n - 1) rep(j, 0, tot) rep(k, 1, tot) {
        if(!(st[j] & st[k]) && dp[i][j][k]) {
            rep(l, 0, tot) {
                if(!(st[l] & st[j]) && !(st[l] & st[k]) && !(st[l] & dat[i + 1]))
                    dp[i + 1][k][l] = max(dp[i + 1][k][l], dp[i][j][k] + sum[l]);
            }
        }
    }

    rep(i, 0, tot) rep(j, 0, tot) res = max(res, dp[n][i][j]);
    printf("%d\n", res);
    return 0;
}

复杂的整数划分问题

描述

将正整数n 表示成一系列正整数之和,n=n1+n2+…+nk, 其中n1>=n2>=…>=nk>=1 ,k>=1 。
正整数n 的这种表示称为正整数n 的划分。

输入

标准的输入包含若干组测试数据。每组测试数据是一行输入数据,包括两个整数N 和 K。 
(0 < N <= 50, 0 < K <= N)

输出

对于每组测试数据,输出以下三行数据:
第一行: N划分成K个正整数之和的划分数目
第二行: N划分成若干个不同正整数之和的划分数目
第三行: N划分成若干个奇正整数之和的划分数目

样例输入

5 2

样例输出

2
3
3

提示

第一行: 4+1, 3+2,
第二行: 5,4+1,3+2
第三行: 5,1+1+3, 1+1+1+1+1+1

整数划分问题是个经典问题:

将n划分成若干正整数之和的划分数。
将n划分成k个正整数之和的划分数。
将n划分成最大数不超过k的划分数。
将n划分成若干个 奇正整数之和的划分数。
将n划分成若干不同整数之和的划分数。

一 求将n划分为若干正整数之和的划分数

1. 若划分的多个整数可以相同

  设dp[i][j]为将i划分为不大于j的划分数

  (1) 当i<j 时,i不能划分为大于i的数,所以dp[i][j]=dp[i][i];

  (2) 当i>j 时,可以根据划分中是否含有j分为两种情况。若划分中含有j,划分方案数为dp[i-j][j];若划分数中不含j,相当于将i划分为不大于j-1的划分数,为dp[i][j-1]。所以当i>j时dp[i][j]=dp[i-j][j]+dp[i][j-1];

  (3) 当i=j 时,若划分中含有j只有一种情况,若划分中不含j相当于将i划分为不大于j-1的划分数。此时dp[i][j]=1+dp[i][j-1]。

dp[n][n]可以解决问题1,dp[n][k]表示将n划分为最大数不超过k的划分数,可以解决问题3。

2. 若划分的正整数必须不同

  设dp[i][j]为将i划分为不超过j的不同整数的划分数

  (1) 当i<j时,i不能划分为大于i的数,所以dp[i][j]=dp[i][i];

  (2) 当i>j时,可以根据划分中是否含有j分为两种情况。若划分中含有j,则其余的划分中最大只能是j-1,方案数为dp[i-j][j-1];若划分中不含j,相当于将i划分为不大于j-1的划分数,为dp[i][j-1]。所以当i>j时dp[i][j]=dp[i-j][j-1]+dp[i][j-1];

  (3) 当i=j时,若划分中含有j只有一种情况,若划分中不含j相当于将i划分为不大于j-1的划分数。此时dp[i][j]=1+dp[i][j-1]

dp[n][n]表示将n划分为不同整数的划分数,可以解决问题5.

二 将n划分为k个整数的划分数

设dp[i][j]为将i划分为j个整数的划分数。

  (1) i<j为不可能出现的情况,dp[i][j]=0;

  (2) 若i=j,有一种情况:i可以划分为i个1之和,dp[i][j]=1;

  (3) 若i>j,可以根据划分数中是否含有1分为两类:若划分数中含有1,可以使用“截边法”将j个划分分别截去一个1,把问题转化为i-j的j-1个划分数,为dp[i-j][j-1]; 若划分中不包含1,使用“截边法”将j个划分数的最下面一个数截去,将为题转化为求i-j的j个划分数,为dp[i-j][j]。所以i>j时dp[i][j]=dp[i-j][j-1]+dp[i-j][j]。

dp[n][k]为将n划分为k个整数的划分数,可解决问题2。

三 将n划分为若干正奇数之和的划分数

设f[i][j]为将i划分为j个奇数之和的划分数,g[i][j]为将i划分为j个偶数之和的划分数。

使用截边法,将g[i][j]的j个划分都去掉1,可以得到f[i-j][j],所以

g[i][j] = f[i-j][j]。

f[i][j]中有包含1的划分方案和不包含1的划分方案。对于包含1的划分方案,可以将1的划分除去,转化为“将i-1划分为j-1个奇数之和的划分数”,即f[i-1][j-1];对于不包含1的划分方案,可以使用截边法对j个划分每一个都去掉一个1,转化为“将i-j划分为j个偶数之和的划分数”,即g[i-j][j]。

所以f[i][j]=f[i-1][j-1]+g[i-j][j]。

f[n][0]+f[n][1]+……+f[n][n]为将n划分为若干奇数的划分数,为问题4的答案。

转载链接:http://www.cnblogs.com/wanghetao/archive/2013/11/25/3442192.html

#include <queue>
#include <cstdio>
#include <cstring>
#include <utility>
#include <iostream>
#include <map>
#include <algorithm>
#include <cmath>
#include <string>
#include <vector>
using namespace std;
typedef pair<int, int> P;
typedef long long ll;
#define N 110
#define M 3645
const int INF = 0x3f3f3f3f;
const double eps = 1e-5;
const double PI = acos(-1);
#define fi first
#define se second
#define rep(i, lll, nnn) for(int i = (lll); i <= (nnn); i++)

int n, k;
int dp[N][N];
///http://www.cnblogs.com/wanghetao/archive/2013/11/25/3442192.html
int main()
{
 //   freopen("data.txt", "r", stdin);

    while(~scanf("%d%d", &n, &k)) {
        memset(dp, 0, sizeof dp);
        dp[0][0] = 1;
        dp[1][1] = 1;
        rep(i, 1, n) rep(j, 1, i) dp[i][j] = dp[i - j][j] + dp[i - 1][j - 1];
        cout << dp[n][k] << endl;

        memset(dp, 0, sizeof dp);
        dp[0][0] = 1;
        rep(i, 1, n) dp[1][i] = dp[0][i] = 1;
        rep(i, 1, n) rep(j, 1, n)
            if(i < j) dp[i][j] = dp[i][i];
            else if(i > j) dp[i][j] = dp[i - j][j - 1] + dp[i][j - 1];
            else if(i == j) dp[i][j] = 1 + dp[i][j - 1];
        cout << dp[n][n] << endl;

        int f[N][N] = {0}, g[N][N] = {0};
        f[0][0] = 0; g[0][0] = 1;
        rep(i, 1, n) f[i][1] = i & 1, g[i][1] = !(i & 1);
        rep(i, 1, n) f[i][i] = 1, g[i][i] = 0;
        rep(i, 1, n) rep(j, 1, n) {
            if(i == j) continue;
            if(i < j) {
                f[i][j] = 0; g[i][j] = 0;
            }
            if(i > j) {
                g[i][j] = f[i - j][j];
                f[i][j] = f[i - 1][j - 1] + g[i - j][j];
            }
        }
        int ans = 0;
        rep(i, 1, n) ans += f[n][i];
        cout << ans << endl;
//        rep(i, 1, n) cout << f[n][i] << ' ';
//        cout << endl;
    }

    return 0;
}

​​​​​​

开餐馆

描述

北大信息学院的同学小明毕业之后打算创业开餐馆.现在共有n 个地点可供选择。小明打算从中选择合适的位置开设一些餐馆。这 n 个地点排列在同一条直线上。我们用一个整数序列m1, m2, ... mn 来表示他们的相对位置。由于地段关系,开餐馆的利润会有所不同。我们用pi 表示在mi 处开餐馆的利润。为了避免自己的餐馆的内部竞争,餐馆之间的距离必须大于k。请你帮助小明选择一个总利润最大的方案。

输入

标准的输入包含若干组测试数据。输入第一行是整数T (1 <= T <= 1000) ,表明有T组测试数据。紧接着有T组连续的测试。每组测试数据有3行,
第1行:地点总数 n (n < 100), 距离限制 k (k > 0 && k < 1000).
第2行:n 个地点的位置m1 , m2, ... mn ( 1000000 > mi > 0 且为整数,升序排列)
第3行:n 个地点的餐馆利润p1 , p2, ... pn ( 1000 > pi > 0 且为整数)

输出

对于每组测试数据可能的最大利润

样例输入

2
3 11
1 2 15
10 2 30
3 16
1 2 15
10 2 30

样例输出

40
30

dp (i)表示从0到i的最大利润。如果没有k直接贪心就行了。有k的话当前的状态只能从距离它大于k的状态转移过来。

这题还要注意状态的初始化应该是1~k的的状态都要初始化,因为这些状态不能有前面的转移过来。

#include <queue>
#include <cstdio>
#include <cstring>
#include <utility>
#include <iostream>
#include <map>
#include <algorithm>
#include <cmath>
#include <string>
#include <vector>
using namespace std;
typedef pair<int, int> P;
typedef long long ll;
#define N 100010
#define M 3645
const int INF = 0x3f3f3f3f;
const double eps = 1e-5;
const double PI = acos(-1);
#define fi first
#define se second
#define rep(i, lll, nnn) for(int i = (lll); i <= (nnn); i++)

int n, a[N], b[N], k, dp[N], ans;
///dp i 表示从0到i的最大利润。
int main()
{
 //   freopen("data.txt", "r", stdin);

    int t;
    scanf("%d", &t);
    while(t--) {
        scanf("%d%d", &n, &k);
        memset(dp, 0, sizeof dp);
        ans = 0;
        rep(i, 1, n) scanf("%d", &a[i]);
        rep(i, 1, n) scanf("%d", &b[i]);
        rep(i, 1, k) {
            dp[i] = b[i];
            ans = max(ans, b[i]);
        }

        rep(i, 1, n) rep(j, 1, i) if(abs(a[i] - a[j]) > k) {
            dp[i] = max(dp[i], dp[j] + b[i]);
            ans = max(dp[i], ans);
        }
        printf("%d\n", ans);
    }

    return 0;
}

硬币

描述

宇航员Bob有一天来到火星上,他有收集硬币的习惯。于是他将火星上所有面值的硬币都收集起来了,一共有n种,每种只有一个:面值分别为a1,a2… an。 Bob在机场看到了一个特别喜欢的礼物,想买来送给朋友Alice,这个礼物的价格是X元。Bob很想知道为了买这个礼物他的哪些硬币是必须被使用的,即Bob必须放弃收集好的哪些硬币种类。飞机场不提供找零,只接受恰好X元。

输入

第一行包含两个正整数n和x。(1 <= n <= 200, 1 <= x <= 10000)
第二行从小到大为n个正整数a1, a2, a3 … an (1 <= ai <= x)

输出

第一行是一个整数,即有多少种硬币是必须被使用的。
第二行是这些必须使用的硬币的面值(从小到大排列)。

样例输入

5 18
1 2 3 5 10

样例输出

2
5 10

提示

输入数据将保证给定面值的硬币中至少有一种组合能恰好能够支付X元。
如果不存在必须被使用的硬币,则第一行输出0,第二行输出空行。

百度了一下,这题思路有点奇葩,先要求出n个硬币组成x的方案个数f[x],再求出去掉某个硬币i的方案个数g[i][x],如果g[i][x]为0那么硬币i肯定要被使用。Orz,神奇的大佬想法。

#include <queue>
#include <cstdio>
#include <cstring>
#include <utility>
#include <iostream>
#include <map>
#include <algorithm>
#include <cmath>
#include <string>
#include <vector>
using namespace std;
typedef pair<int, int> P;
typedef long long ll;
#define N 21000
#define M 3645
const int INF = 0x3f3f3f3f;
const double eps = 1e-5;
const double PI = acos(-1);
#define fi first
#define se second
#define rep(i, lll, nnn) for(int i = (lll); i <= (nnn); i++)

int n, x, a[N], g[N], f[N], ans[N], l;
int main()
{
   // freopen("data.txt", "r", stdin);

    while(~scanf("%d%d", &n, &x)) {
        rep(i, 1, n) scanf("%d", &a[i]);
        sort(a + 1, a + 1 + n);
        memset(f, 0, sizeof f);
        f[0] = 1;
        rep(i, 1, n) for(int j = x; j >= a[i]; j--) {
            f[j] += f[j - a[i]];
        }
        l = 0;
        rep(i, 1, n) {
            memset(g, 0, sizeof g);
            rep(j, 0, x) {
                if(j >= a[i]) g[j] = f[j] - g[j - a[i]];
                else g[j] = f[j];
            }
            if(!g[x]) ans[l++] = a[i];
        }
        printf("%d\n", l);
        rep(i, 0, l - 1) printf("%d%c", ans[i], i == l - 1?'\n':' ');
    }

    return 0;
}

上机

又到周末了,同学们陆陆续续开开心心的来到机房上机。jbr也不例外,但是他到的有点晚,发现有些机位上已经有同学正在做题,有些机位还空着。细心的jbr发现,一位同学来到机房,坐在机位i上,如果他的左右两边都空着,他将获得能力值a[i];如果当他坐下时,左边或者右边已经有一个人在上机了,他将获得能力值b[i];如果当他坐下时,他的左边右边都有人在上机,他将获得能力值c[i]。

同时他发现,已经在上机的同学不会受到刚要坐下的同学的影响,即他们的能力值只会在坐下时产生,以后不会发生变化;第一个机位左边没有机位,最后一个机位右边没有机位,无论何时坐在这两个机位上将无法获得c值。

这时jbr发现有一排机器还空着,一共有N个机位,编号1到N。这时有N位同学们陆陆续续来到机房,一个一个按照顺序坐在这排机位上。聪明的jbr想知道怎么安排座位的顺序,可以使这N位同学获得能力值的和最大呢?

输入

第一行一个整数N(1<= N <= 10000)

第二行N个数,表示a[i]

第三行N个数,表示b[i]

第四行N个数,表示c[i]

(1<= a[i],b[i],c[i] <=10000)

输出

一个整数,表示获得最大的能力值和

样例输入

4
1 2 2 4
4 3 3 1
2 1 1 2

样例输出

14

提示

第一位同学坐在第四个机位上,获得能力值4;
第二位同学坐在第三个机位上,获得能力值3;
第三位同学坐在第二个机位上,获得能力值3;
第四位同学坐在第一个机位上,获得能力值4;
总和为14。

这题我想一个人坐下去是受到左右两边的人的影响啊,没有一个“序”啊,做不了啊,凉了啊……度娘下来没看到题解只有代码,死了无数脑细胞终于明白又是要转换思维,比如相邻位置ijk,人def,e先坐下到j位置然后ef再坐下ik位置的能力值是a[j]+b[i]+b[k],可以看成d先坐下拿了b[k](是的,这时候e右边没人,老子就要改规则)然后e坐拿a[j]然后f坐下拿b[k]。这就有顺序了!!!!!然后一个人坐下的状态有四种——左无右无,左有右无,左无右有,左有右有。对应成0123。具体的状态转移方程不想写了看代码吧。

#include <queue>
#include <cstdio>
#include <cstring>
#include <utility>
#include <iostream>
#include <map>
#include <algorithm>
#include <cmath>
#include <string>
#include <vector>
using namespace std;
typedef pair<int, int> P;
typedef long long ll;
#define N 11000
#define M 3645
const int INF = 0x3f3f3f3f;
const double eps = 1e-5;
const double PI = acos(-1);
#define fi first
#define se second
#define rep(i, lll, nnn) for(int i = (lll); i <= (nnn); i++)

int dp[N][4];
int a[N], b[N], c[N];
int n;
///0 zwyw 1 zyyw 2 zwyy 3 zyyy
//dp[i][0] = max(dp[i - 1][3], dp[i - 1][2]) + a[i]
//dp[i][1] = max(dp[i - 1][1], dp[i - 1][0]) + b[i]
//dp[i][2] = max(dp[i - 1][2], dp[i - 1][3]) + b[i]
//dp[i][3] = max(dp[i - 1][0], dp[i - 1][1]) + c[i]

int main()
{
    //freopen("data.txt", "r", stdin);
    scanf("%d", &n);
    rep(i, 1, n) scanf("%d", &a[i]);
    rep(i, 1, n) scanf("%d", &b[i]);
    rep(i, 1, n) scanf("%d", &c[i]);

    dp[1][0] = a[1];
    dp[1][1] = -1;
    dp[1][2] = b[1];
    dp[1][3] = -1;
    rep(i, 2, n) {
        dp[i][0] = max(dp[i - 1][3], dp[i - 1][2]) + a[i];
        dp[i][1] = max(dp[i - 1][1], dp[i - 1][0]) + b[i];
        dp[i][2] = max(dp[i - 1][2], dp[i - 1][3]) + b[i];
        dp[i][3] = max(dp[i - 1][0], dp[i - 1][1]) + c[i];
    }

    printf("%d\n", max(dp[n][0], dp[n][1]));

    return 0;
}

Charm Bracelet

总时间限制: 

1000ms

内存限制: 

65536kB

描述

Bessie has gone to the mall's jewelry store and spies a charm bracelet. Of course, she'd like to fill it with the best charms possible from the N(1 ≤ N≤ 3,402) available charms. Each charm iin the supplied list has a weight Wi(1 ≤ Wi≤ 400), a 'desirability' factor Di(1 ≤ Di≤ 100), and can be used at most once. Bessie can only support a charm bracelet whose weight is no more than M(1 ≤ M≤ 12,880).

Given that weight limit as a constraint and a list of the charms with their weights and desirability rating, deduce the maximum possible sum of ratings.

输入

Line 1: Two space-separated integers: N and M
Lines 2..N+1: Line i+1 describes charm i with two space-separated integers: Wi and Di

输出

Line 1: A single integer that is the greatest sum of charm desirabilities that can be achieved given the weight constraints

样例输入

4 6
1 4
2 6
3 12
2 7

样例输出

23

还是背包好。

#include <queue>
#include <cstdio>
#include <cstring>
#include <utility>
#include <iostream>
#include <map>
#include <algorithm>
#include <cmath>
#include <string>
#include <vector>
using namespace std;
typedef pair<int, int> P;
typedef long long ll;
#define N 3410
#define M 12885
const int INF = 0x3f3f3f3f;
const double eps = 1e-5;
const double PI = acos(-1);
#define fi first
#define se second
#define rep(i, lll, nnn) for(int i = (lll); i <= (nnn); i++)

int n, m, w[N], d[N], dp[M], ans;
///dpi,j =  dp i + 1, j - w[i] + d[i]
int main()
{
    //freopen("data.txt", "r", stdin);

    while(~scanf("%d%d", &n, &m)) {
        rep(i, 1, n) scanf("%d%d", &w[i], &d[i]);
        memset(dp, 0, sizeof dp);
        ans = 0;
        rep(i, 1, n) {
            for(int j = m; j >= w[i]; j--) {
                dp[j] = max(dp[j], dp[j - w[i]] + d[i]);
            }
        }
        printf("%d\n", dp[m]);
    }

    return 0;
}

滑雪

描述

Michael喜欢滑雪百这并不奇怪, 因为滑雪的确很刺激。可是为了获得速度,滑的区域必须向下倾斜,而且当你滑到坡底,你不得不再次走上坡或者等待升降机来载你。Michael想知道载一个区域中最长的滑坡。区域由一个二维数组给出。数组的每个数字代表点的高度。下面是一个例子

 1  2  3  4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9


一个人可以从某个点滑向上下左右相邻四个点之一,当且仅当高度减小。在上面的例子中,一条可滑行的滑坡为24-17-16-1。当然25-24-23-...-3-2-1更长。事实上,这是最长的一条。

输入

输入的第一行表示区域的行数R和列数C(1 <= R,C <= 100)。下面是R行,每行有C个整数,代表高度h,0<=h<=10000。

输出

输出最长区域的长度。

样例输入

5 5
1 2 3 4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9

样例输出

25

这题也是难在没有一个天然的序,这就很难看出这是个dp……知道这是个dp就不难写了。老师上课讲是sort一下然后让低的往高的递推。只能喊666.

#include <queue>
#include <cstdio>
#include <cstring>
#include <utility>
#include <iostream>
#include <map>
#include <algorithm>
#include <cmath>
#include <string>
#include <vector>
using namespace std;
typedef pair<int, int> P;
typedef long long ll;
#define N 110
#define M 3645
const int INF = 0x3f3f3f3f;
const double eps = 1e-5;
const double PI = acos(-1);
#define fi first
#define se second
#define rep(i, lll, nnn) for(int i = (lll); i <= (nnn); i++)

int a[N][N], r, c;
struct node {
    int w, x, y;
    bool operator < (const node & t) const{
        return w < t.w;
    }
}b[N * N];
int dp[N][N];
int dx[] = {0, 0, 1, -1};
int dy[] = {1, -1, 0, 0};

int main()
{
    //freopen("data.txt", "r", stdin);

    while(~scanf("%d%d", &r, &c)) {
        rep(i, 1, r) rep(j, 1, c) {
            scanf("%d", &a[i][j]);
            b[(i - 1) * c + j].x = i;
            b[(i - 1) * c + j].y = j;
            b[(i - 1) * c + j].w = a[i][j];
            dp[i][j] = 1;
        }
        sort(b + 1, b + 1 + r * c);
      //  rep(i, 1, r * c) printf("%d ", b[i].w);
        int x, y, ans = 1;
        rep(i, 1, r * c) {
            rep(j, 0, 3) {
                x = b[i].x + dx[j]; y = b[i].y + dy[j];
                if(0 >= x || x > r || 0 >= y || y > c) continue;
                if(a[x][y] >= b[i].w) continue;
                dp[b[i].x][b[i].y] = max(dp[b[i].x][b[i].y], dp[x][y] + 1);
                ans = max(ans, dp[b[i].x][b[i].y]);
              //  cout << dp[b[i].x][b[i].y] <<' ' <<i << ' ' <<j<< ' ' << x<<' ' <<y<<endl;
            }
        }

        printf("%d\n", ans);
    }

    return 0;
}

最后总结一下的话就是看到dp我还是不会做。。。

猜你喜欢

转载自blog.csdn.net/qq_40379678/article/details/81260181
今日推荐