DP_1

算法课第五次作业
BJTU计算思维与算法实训平台

A.合唱队形

题目描述
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

解题思路

此问题是LIS问题的变形,即把整数列分成左右两半,求左半边的LIS(Longest Increasing Subsequence),右半边的LDS(Longest Decreasing Subsequence),长度之和最大状态即为解

#include <iostream>
#include <cstring>
#define MAX_N 1000
using namespace std;
int n;
int a[MAX_N];
int dp[MAX_N],dp2[MAX_N];
//a[0]~a[k-1]
int lis(int k) {
    int res = 0;
    for (int i = 0; i < k; i++) {
        dp[i] = 1;
        for (int j = 0; j < k; j++) {
            if (a[i] > a[j]) {
                dp[i] = max(dp[i], dp[j] + 1);
            }
            res = max(res, dp[i]);
        }
    }
    return res;
}
//a[k]~a[n-1]
int lds(int k) {
    int res = 0;
    for (int i = k; i < n; i++) {
        dp2[i] = 1;
        for (int j = k; j < n; j++) {
            if (a[i] < a[j]) {
                dp2[i] = max(dp2[i], dp2[j] + 1);
            }
            res = max(res, dp2[i]);
        }
    }
    return res;
}
int main() {
    cin >> n;
    for(int i = 0; i < n; i++)
        cin >> a[i];
    int sol = 0;
    for(int i = 0; i < n; i++){
        sol = max(sol, lis(i) + lds(i));
        memset(dp,0,sizeof(dp));
        memset(dp2,0,sizeof(dp2));
    }
    cout << n - sol;
    return 0;
}

优化思路

函数lis()和lds()显然可以合一

B.加分二叉树

题目描述
  设一个n个节点的二叉树tree的中序遍历为(l,2,3,…,n),其中数字1,2,3,…,n为节点编号。每个节点都有一个分数(均为正整数),记第i个节点的分数为di,tree及它的每个子树都有一个加分,任一棵子树subtree(也包含tree本身)的加分计算方法如下:
  subtree的左子树的加分× subtree的右子树的加分+subtree的根的分数
  若某个子树为空,规定其加分为1,叶子的加分就是叶节点本身的分数。不考虑它的空子树。
  试求一棵符合中序遍历为(1,2,3,…,n)且加分最高的二叉树tree。要求输出;
  (1)tree的最高加分
  (2)tree的前序遍历
输入数据
第 1 行:一个整数 n (n<30),为节点个数。
第 2 行: n 个用空格隔开的整数,为每个节点的分数(分数<100)。
输出数据第 1 行:一个整数,为最高加分(结果不会超过 4,000,000,000 )。
第 2 行: n 个用空格隔开的整数,为该树的前序遍历。
若存在多种前序遍历均为最高加分,则输出字典序最小的前序遍历
样例输入
5
5 7 1 2 10
样例输出
145
3 1 2 4 5

解题思路

区间动态规划
由于二叉树的中序遍历为1, 2, 3, ..., n,因此该二叉树的特点是:当根节点为k时,编号小于k的结点都在其左子树上,而大于k的结点都在其右子树上。这个特点符合区间动态规划的特性。
两个dp数组dp[i][j]存从i到j的最大权值
root[i][j]存i到j的最优根(方便输出)

#include <iostream>
#include <cstring>
#define N 31
using namespace std;
typedef long long ll;
//di < 4,000,000,000
ll n, dp[N][N], root[N][N];

void output(int l, int r) {
    if(l>r) return;
    cout<<root[l][r]<<" ";
    if(l==r) return;
    output(l,root[l][r]-1);
    output(root[l][r]+1,r);
}
int main() {
    cin>>n;
    //init d[i] f[i]
    memset(dp,0,sizeof(dp));
    for(int i=1; i<=n; i++) {
        cin>>dp[i][i];
        root[i][i] = i;
    }
    //对len长的树
    for(int len=1; len<n; len++) {
        for(int i=1; i+len<=n; i++) {
            int j=i+len;
            for(int k=i; k<j; k++) {
                ll maxw = dp[i][k-1]*dp[k+1][j]+dp[k][k];
                if(!dp[i][k-1]) maxw = dp[k+1][j]+dp[k][k];
                if(!dp[k+1][j]) maxw = dp[i][k-1]+dp[k][k];
                if(dp[i][j]<maxw) {
                    dp[i][j]=maxw;
                    root[i][j]=k;
                }
            }
        }
    }
    cout<<dp[1][n]<<endl;
    output(1,n);
    return 0;
}

C.采药

01背包问题,略

D.数的划分

题目描述
将整数n分成k份,且每份不能为空,任意两份不能相同(不考虑顺序)。
例如:n=7,k=3,下面三种分法被认为是相同的。
1,1,5; 1,5,1; 5,1,1;
问有多少种不同的分法。
输入数据
输入 n , k (6< n≤ 200 , 2≤ k≤ 6)
输出数据
一个整数,即不同的分法。
样例输入
7 3
样例输出
4

思路

划分数问题描述

n个无区别实体划分为不超过m组求方法数

递推关系如下(dp[i][j]表示j的i划分)

dp[ i ][ j ] = dp[i - 1][ j ] + dp[ i ][ j - i ]

即j的i-1划分加i的j-i划分
则本题代码如下

#include<iostream>
#include<cstring>
using namespace std;
#define MAX_N 200
#define MAX_M 6
int dp[MAX_M][MAX_N];//n的m划分
int n,m;
int main() {
    cin>>n>>m;
    n-=m;
    memset(dp,0,sizeof(dp));
    int res=0;
    dp[0][0] = 1;
    //dp[i][j];j的i划分
    for(int i = 1; i <= m; i++) {
        for(int j = 0; j <= n; j++) {
            if(j - i >= 0)
                dp[i][j] = (dp[i - 1][j] + dp[i][j - i]);
            else
                dp[i][j] = dp[i - 1][j];
        }
    }
    cout<<dp[m][n];
    return 0;
}

E.统计单词个数

题目描述
  给出一个长度不超过200的由小写英文字母组成的字母串(约定;该字串以每行20个字母的方式输入,且保证每行一定为20个)。要求将此字母串分成k份(1< k< =40),且每份中包含的单词个数加起来总数最大(每份中包含的单词可以部分重叠。当选用一个单词之后,其第一个字母不能再用。例如字符串this中可包含this和is,选用this之后就不能包含th)。
  单词在给出的一个不超过6个单词的字典中。
  要求输出最大的个数。
输入数据
第一行有二个正整数 (p , k)
p 表示字串的行数;
k 表示分为 k 个部分。
接下来的 p 行,每行均有 20 个字符。
再接下来有一个正整数 s ,表示字典中单词个数。 (1≤ s≤ 6)
接下来的 s 行,每行均有一个单词。
输出数据
输出一个整数,即最大的个数
样例输入
1 3
thisisabookyouareaoh
4
is
a
ok
sab
样例输出
7

思路

先确定用什么表示状态,因为递推过程中有两个变量——当前字母的位置,以及所分的部分。
那么dp[i][j]表示到第i个字母为止,分成j份 的最多单词数。
核心算法是
for(int i=1;i<=n;i++)
  for(int j=1;j<=m;j++)
    for(int k=i;k>=j;k--)
      dp[i][j]=max(dp[i][j],dp[k-1][j-1]+w);
w表示[k~i]这个区间的单词数,可用调用api实现:
在做这道题之前,先了解以下几个的STL函数。

  • s.substr(x,y) 在s中取出下标从x开始长度为y的字符串(第一个字母下标为0);
  • s.find(t) 在s中寻找字符串t,找到则返回0,找不到则返回一个极大值;
  • s.length() 返回字符串s的长度;

题目要求说一个字母只能是一个单词的开头。
那么我们考虑倒序。
首先,我们从右往左枚举区间右端点。
然后从在右往左枚举区间左端点,先继承上一个左端点的sum,在判断当前的这个左端点是否能作为某个单词的开头。
如果能就+1.

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
using namespace std;
int dp[1000][100],sum[1000][1000];
int p,k,m,len;
string s="0",ch,a[1000];
inline bool check(int l,int r){
    string x=s.substr(l,r-l+1);
    for(int i=1;i<=m;i++)
        if(x.find(a[i])==0) return true;
    return false;
}
int main()
{
    int i,j,t;
    scanf("%d%d",&p,&k);
    for(i=1;i<=p;i++){
        cin>>ch;
        s+=ch;
    }
    scanf("%d",&m);
    for(i=1;i<=m;i++) cin>>a[i];
    len=s.length()-1;
    for(i=len;i>=1;i--)
    for(j=i;j>=1;j--){
        sum[j][i]=sum[j+1][i];
        if(check(j,i)) 
        sum[j][i]++;
    }
    for(i=1;i<=len;i++)
        for(j=1;j<=k&&j<=i;j++)
            for(t=j;t<=i;t++)//dp核心
                dp[i][j]=max(dp[i][j],dp[t-1][j-1]+sum[t][i]);
    printf("%d",dp[len][k]);
}

(转自文雨淋)

F.马兰过河卒

题目描述
  棋盘上A点有一个过河卒,需要走到目标B点。卒行走的规则:可以向下、或者向右。同时在棋盘上C点有一个对方的马,该马所在的点和所有跳跃一步可达的点称为对方马的控制点。因此称之为“马拦过河卒”。
  棋盘用坐标表示,A点(0, 0)、B点(n, m)(n, m为不超过15的整数),同样马的位置坐标是需要给出的。现在要求你计算出卒从A点能够到达B点的路径的条数,假设马的位置是固定不动的,并不是卒走一步马走一步。
输入数据
一行四个数据,分别表示 B 点坐标和马的坐标。
输出数据
一个数据,表示所有的路径条数。
样例输入
6 6 3 3
样例输出
6

思路

考虑棋盘没🐎,卒(0,0)到达点(n,n)的路径数,显然等于到(n,n-1)加到(n-1,n)的路径数,递推关系就有了,顺便加上🐎这个限制条件便得到解

#include<cstring>
#include<iostream>
using namespace std;
const int dx[] = {-1,-1,1,1,2,2,-2,-2};
const int dy[] = {2,-2,2,-2,1,-1,1,-1};
bool chess[16][16];//chess[0][0] ~ chess[15][15]
int dp[16][16];//dp[x][y] = dp[x-1][y] + dp[x][y-1]
int ex,ey,mx,my;
bool inchess(int x,int y) {
    return (x>-1 && x<16 && y>-1 && y<16);
}
int main() {
    memset(chess,true,sizeof(chess));
    memset(dp,0,sizeof(dp));
    cin >> ex >> ey >> mx >> my;
    chess[mx][my] = false;
    dp[0][0] = 1;
    for(int i=0; i<8; i++)
        if(inchess(mx+dx[i],my+dy[i]))
            chess[mx+dx[i]][my+dy[i]] = false;
    dp[0][0] = 1;
    for(int i=1; i<=ex; i++) {
        if(chess[i][0])
            dp[i][0] = dp[i-1][0];
        else dp[i][0] = 0;
    }
    for(int j=1; j<=ey; j++) {
        if(chess[0][j])
            dp[0][j] = dp[0][j-1];
        else dp[0][j] = 0;
    }
    for(int i=1; i<=ex; i++) {
        for(int j=1; j<=ey; j++) {
            if(chess[i][j]) {
                dp[i][j] = dp[i-1][j] + dp[i][j-1];
            } else dp[i][j] = 0;
        }
    }
    cout << dp[ex][ey];
    return 0;
}

代码欠优化。空间,代码量都不佳。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,001评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,210评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,874评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,001评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,022评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,005评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,929评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,742评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,193评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,427评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,583评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,305评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,911评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,564评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,731评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,581评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,478评论 2 352

推荐阅读更多精彩内容

  • 在C语言中,五种基本数据类型存储空间长度的排列顺序是: A)char B)char=int<=float C)ch...
    夏天再来阅读 3,340评论 0 2
  • 树形动态规划,顾名思义就是树+DP,先分别回顾一下基本内容吧:动态规划:问题可以分解成若干相互联系的阶段,在每一个...
    Mr_chong阅读 1,481评论 0 2
  • 计算机二级C语言上机题库(南开版) 1.m个人的成绩存放在score数组中,请编写函数fun,它的功能是:将低于平...
    MrSunbeam阅读 6,350评论 1 42
  • 1.01背包 题目描述 有 n 个重量个价值分别为 w_i, v_i 的物品。从这些物品中选出总重量不超过 W 的...
    一只可爱的柠檬树阅读 437评论 0 2
  • 年近30,感觉这年过得越来越没年味。年前,我就在想,如何让这年过得像年样。 为此做出了几个过年的原则: 一、过年期...
    践行程序阅读 505评论 1 1