“醉菜”动规

“最菜”动规


P2014 [CTSC1997]选课

题目描述
在大学里每个学生,为了达到一定的学分,必须从很多课程里选择一些课程来学习。在课程里有些课程必须在某些课程之前学习,如高等数学总是在其他课程之前学习,现在有 N 门功课,每门课有学分,每门课有一门或没有直接先修课(若课程 a 是课程 b 的先修课,即只有学完了课程 a ,才能学习课程 b )。一个学生要从这些课程里选择 M 门课程学习,问他能获得的最大学分是多少?

输入格式
第一行有两个整数 N , M 用空格隔开(1 ≤ N ≤ 300,1 ≤ M ≤ 300)。
接下来的 N 行,每行包含两个整数 ki 和 si , ki 表示第 i 门课的直接先修课, si表示第 i 门课的学分。
若 ki 为零表示没有直接先修课(1 ≤ ki ≤ N,1 ≤ si ≤ 20)。

输出格式
只有一行,选 M 门课程的最大可得学分。

输入输出样例
输入 #
7 4
2 2
0 1
0 4
2 1
7 1
7 6
2 2
输出 #
13

分析

==============================================================================
部分课程形成树,全部课程形成森林
在这里插入图片描述
为方便处理,设置虚拟根结点,将各个树的根连接至该虚拟结点
在这里插入图片描述

==============================================================================
单独拿出⑦,⑤和⑥,作为整体进行分析(过程一)
在这里插入图片描述
以下情况最高可得学分
选择一门课程:⑦ - (2)
选择两门课程:⑦⑥ - (2+6)
选择三门课程:⑦⑥⑤ - (2+6+1)
选择四门课程:⑦⑥⑤ - (2+6+1)

==============================================================================
单独拿出②,①,④,⑦,⑤和⑥,作为整体进行分析(过程二)
在这里插入图片描述
以下情况可得最高学分
选择一门课程:② - (1)
选择两门课程:②① / ②[⑦] - (1+2)
选择三门课程:②[⑦⑥] - (1+2+6)
选择四门课程:②①[⑦⑥] - (1+2+2+6)
注意:
其中⑦和⑦⑥可分别看作整体
而⑦看作过程一中的选择一门课程的情况
而⑦⑥看作过程一中的选择两门课程的情况

==============================================================================
最后回到整棵树(过程三)
在这里插入图片描述
以下情况可得最高学分
选择一门课程:虚拟结点 - (0)
选择两门课程:③ - (4)
选择三门课程:②③ - (1+4)
选择四门课程:[②①]③ / [②⑦]③ - (1+2+4)
选择五门课程:[②⑦⑥]③ - (1+2+6+4)
得到答案13
注意:
为什么要考虑选择五门课程的情况?
因为虚拟结点必须选上,所以加上题目所要求的课程数量,1+4=5。
补充:
类似地,②①和②⑦和②⑦⑥仍分别看作整体。

==============================================================================
关于状态转移方程:
例如,过程二的选择四门课程的情况
将②,②①,②①④分别相等于②[1],②[2],②[3]
(其实相当于目前在分析父结点②下的孩子结点⑦,所以不包括⑦)
(②[i]都表示选择i门课程下的最高学分)
将⑦,⑦⑥,⑦⑥⑤分别相等于⑦[1],⑦[2],⑦[3]
则,有如下式子
②[4] = max { ②[3] + ⑦[1] , ②[2] + ⑦[2] , ②[1] + ⑦[3] }

太多废话,你看得累我也说得累……

代码

#include <iostream>
using namespace std;

#define MAX 301
int N, M, score[MAX + 1];
int Node[MAX + 1][MAX + 1], pos[MAX + 1];
// Node[i]为第i个结点,Node[i][j]为第i个根的第j个相连结点
// pos[i]用于表示第i个结点的相连结点个数,如果 pos[i] == 1 则Node[i]是叶子结点

int dp[MAX + 1][MAX + 1];

void dfs(int now, int pre) {
    for(int i = 0; i < pos[now]; i++) {  // 遍历Node[now]即第now个根的孩子
        if(dp[now][M + 1] < score[now]) {  // 为后续状态转移方程做准备
            for(int x = 1; x <= M + 1; x++) {
                dp[now][x] = score[now];
            }
        }

        int child = Node[now][i];

        if(child == pre)  // 遇到叶子直接返回
            continue;

        dfs(child, now);  // 深度搜索至叶子

        for(int j = M + 1; j >= 1; j--) {
            for(int k = 1; k < j; k++) {
                dp[now][j] = max(dp[now][j], dp[now][k] + dp[child][j - k]);
            }
        }
    }
}

int main() {
    cin >> N >> M;

    for(int i = 1; i <= N; i++) {
        int j;
        cin >> j >> score[i];

        // 构建树
        Node[j][pos[j]++] = i;
        Node[i][pos[i]++] = j;
    }

    dfs(0, -1);

    cout << dp[0][M + 1];
}

Bribe the Prisoners(2009 Round 1C C)

题目描述
如下图所示,一个监狱里有P个并排着的牢房。从左至右依次编号为1,2,……,P。最初所有的牢房里都住着一个囚犯。相邻的两个牢房之间有一个窗户,,可以通过它与相邻牢房里的囚犯对话。在这里插入图片描述
现在要释放一些囚犯。如果释放某个牢房里的囚犯,其相邻的牢房里的囚犯就会知道,因而发怒暴动。所以,释放某个牢房里的囚犯同时,必须要贿赂两旁相邻牢房里的囚犯一枚金币。另外,为了防止释放的消息在相邻牢房间传开,不仅两旁直接相邻的牢房,所有可能听到消息的囚犯,即直到空牢房为止或直到监狱两端为止,此间的所有囚犯都必须给一枚金币。在这里插入图片描述
现在要释放A1,A2,……,AQ号牢房里的Q名囚犯,释放的顺序还没确定。如果选择所需金币数量精良少的顺序释放,最少需要多少枚金币?

限制条件
·1 ≤ N ≤ 100
·Q ≤ P
Small
·1 ≤ P ≤ 100
·1 ≤ Q ≤ 5
Large
1 ≤ P ≤ 10000
1 ≤ Q ≤ 100

样例1
输入:P = 8,Q = 1,A = { 3 }
输出:7(必须要给剩下的7个人一枚金币)

样例2
输入:P = 20,Q = 3,A = { 3,6,14 }
输出:35(按照顺序14,6,3的顺序释放,则需要19 + 12 + 4即35枚金币,是最少的)

分析

==============================================================================
借用样例2分析
为方便处理,设置0号空牢房及21号空牢房,即A = { 0,3,6,14,21 },从下标零开始

==============================================================================
单独拿出[0,14]区间进行分析
第一次释放所需金币都为12
第二次释放所需金币分别为
先释放A2 = 6,再释放A1 = 3的情况:4
在这里插入图片描述
先释放A1 = 3,再释放A2 = 6的情况:9
在这里插入图片描述
由此可得先释放A2 = 6,再释放A1 = 3,所花金币为16

==============================================================================
对[0,21]区间进行分析
第一次释放所需金币都为19
第二次释放所需金币分别为
先释放A3 = 14,再释放(A1 = 3,A2 = 6)的情况:16
(A1 = 3,A2 = 6)看作整体
在这里插入图片描述
先释放(A1 = 3,A2 = 6),再释放A3 = 14的情况:4 + 13
4的由来:在释放A2 = 6后释放A1 = 3所得的
在这里插入图片描述
由此可得由A3 = 14,A2 = 6,A1 = 3的顺序释放,可使花费金币最少,为35
注意 / 补充
正常比较应该分两组:
(A1 = 3,A2 = 6)与A3 = 14
A1 = 3与(A2 = 6,A3 = 14)

==============================================================================
关于状态转移方程:
Result[A0,A4]((0,21)区间内)
= min { Result[A0,A3] + Result[A3,A4] , Result[A0,A1] + Result[A1,A4] } + A4 - A0 - 2

太多废话,你看得累我也说得累

代码

#include <iostream>
using namespace std;

#define MAX_Q 101
int P, Q, A[MAX_Q + 2];
// A[MAX_Q + 2]中保存输入数据
// 下标从一开始
// 下标[0]为左空牢房或监狱左端
// 下标[Q + 1]为右空牢房或监狱右端

int dp[MAX_Q + 1][MAX_Q + 2];  // 释放(Ai,Aj)所需的至少的金币

void solve() {
    A[0] = 0;  // 左空牢房或监狱左端
    A[Q + 1] = P + 1;  // 右空牢房或监狱右端

    for(int q = 0; q <= Q; q++) {
        dp[q][q + 1] = 0;  //  区间内无即将释放的囚犯
    }

    for(int w = 2; w <= Q + 1; w++) {  // 从短的区间开始
        for(int i = 0; i + w <= Q + 1; i++) {
            int j = i + w, t = INT_MAX;

            for(int k = i + 1; k < j; k++) {  // 计算第二人释放时最少的金币
                t = min(t, dp[i][k] + dp[k][j]);
            }

            dp[i][j] = A[j] - A[i] - 2 + t;  // A[j] - A[i] - 2 为释放第一人时的金币
        }
    }

    cout << dp[0][Q + 1];
}

int main() {
    cin >> P >> Q;
    for(int i = 1; i <= Q; i++) {
        cin >> A[i];
    }
    solve();
}

(゚Д゚≡゚д゚)!?

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值