目录
HDU1011——Starship Troopers(树形DP)
HDU1011——Starship Troopers(树形DP)
题目描述
运行代码
#include <iostream>
#include <vector>
using namespace std;
const int MAXN = 110;
int n, m;
int cost[MAXN], weight[MAXN];
int dp[MAXN][MAXN];
bool visited[MAXN];
vector<int> tree[MAXN]; // 建树
// 计算向上取整的除法结果
int ceilDiv(int num, int div) {
return (num + div - 1) / div;
}
// 比较两个整数并返回最大值
int maxValue(int a, int b) {
return a > b ? a : b;
}
// 深度优先搜索函数
void dfs(int p) {
int i, j, k;
int temp = ceilDiv(cost[p], 20); // 攻打 p 至少需要 temp 个人
for (i = temp; i <= m; i++)
dp[p][i] = weight[p];
visited[p] = true; // 记录下已经处理 p 节点, 强制只能顺着树往下撸
for (i = 0; i < tree[p].size(); i++) {
int t = tree[p][i];
if (visited[t])
continue;
dfs(t);
for (j = m; j > temp; j--) { // 树形背包, 容量从后向前推
for (k = 1; k <= j - temp; k++) // 留下 j - k 攻打 p, k 攻打儿子
dp[p][j] = maxValue(dp[p][j], dp[p][j - k] + dp[t][k]);
}
}
}
int main() {
int i;
while (cin >> n >> m, n != -1 || m != -1) {
for (i = 0; i <= n; i++)
tree[i].clear();
memset(dp, 0, sizeof(dp));
memset(visited, false, sizeof(visited));
for (i = 1; i <= n; i++)
cin >> cost[i] >> weight[i];
for (i = 1; i < n; i++) {
int u, v;
cin >> u >> v;
tree[u].push_back(v);
tree[v].push_back(u);
}
if (m == 0) {
cout << "0" << endl;
continue;
}
dfs(1);
cout << dp[1][m] << endl;
}
return 0;
}
代码思路
-
初始化:
MAXN
定义数组的最大尺寸。n
和m
分别代表房间数和星际战士的数量。cost[]
和weight[]
数组分别存储每个房间内虫子的数量(访问成本)和是否可能包含大脑(1表示可能包含,0表示不包含)。dp[][]
是一个二维数组,用于动态规划,存储以一定数量的星际战士在每个房间能访问到的可能含大脑的房间的最大数量。visited[]
用于记录哪些房间已经被访问过。tree[][]
存储树结构,即每个房间连接的其他房间。
-
辅助函数:
ceilDiv()
函数计算向上取整的除法结果,但在这个场景下,它实际上不需要,因为访问成本是固定的,直接比较即可。maxValue()
函数返回两个整数中的较大者。
-
深度优先搜索(DFS):
dfs()
函数递归地遍历树结构。- 计算访问当前房间至少需要的星际战士数量。
- 如果星际战士数量足够,初始化
dp
数组的相应位置为该房间可能含大脑的权重。 - 标记当前房间为已访问。
- 对于当前房间的所有子房间,递归调用
dfs()
。 - 在所有子房间处理完毕后,使用动态规划更新当前房间的
dp
值,考虑子房间的贡献。
-
主函数:
- 循环读取输入,直到遇到结束标志(两个-1)。
- 每次循环前清空树结构和重置
dp
与visited
数组。 - 读取房间描述和连接信息。
- 特殊情况处理:如果
m
为0,则直接输出0。 - 否则,调用
dfs()
函数从根节点开始。 - 输出以
m
个星际战士在根节点(房间1)所能访问到的可能含大脑的房间的最大数量。
树形DP
树形动态规划(Tree Dynamic Programming,简称树形DP)是一种解决在树形结构上的最优化问题的策略,它结合了图论和动态规划技术。树形DP常用于解决以下类型的问题:
- 路径问题:如求树中两点间最长或最短路径、路径上的最大权值等。
- 子树问题:如求具有特定性质的子树的最大或最小值,或者求子树中的某些统计信息。
- 覆盖问题:如用最少的节点覆盖树中的所有节点,或者达到某种条件下的最佳覆盖方案。
树形DP的关键步骤包括:
- 状态定义:确定DP状态,这通常涉及到子树的概念,比如
dp[node][state]
表示以node
为根的子树在某种状态下的最优解。 - 状态转移:在状态定义的基础上,找到父节点状态和子节点状态之间的关系,从而实现状态的传递。
- 初始化:确定DP数组的初始状态,这通常是叶子节点或边界条件。
- 计算顺序:由于树形DP依赖于子问题的解,所以通常采用深度优先搜索(DFS)或广度优先搜索(BFS)遍历树,确保总是先解决子问题再解决父问题。
- 最终答案:确定最终问题的答案对应DP数组中的哪个状态。
树形DP的伪代码示例,用于解决一个关于子树最大值的问题:
def dfs(node):
# 初始化dp[node]为某个初始值
dp[node] = initial_value
# 遍历当前节点的所有子节点
for child in adj_list[node]:
# 递归地解决子节点问题
dfs(child)
# 状态转移:更新当前节点的dp值
dp[node] = max_function(dp[node], dp[child])
# 主函数中调用dfs函数从根节点开始
dfs(root)
adj_list
是一个邻接表,用于表示树的结构;initial_value
是根据具体问题设定的初始值;max_function
是一个函数,用于根据子节点的状态更新当前节点的状态,这通常涉及到某种最优化操作。
HDU1012——u Calculate e
题目描述
运行代码
#include <iostream>
#include <iomanip>
using namespace std;
int factor(int n) {
int result = 1;
for (int i = 1; i <= n; i++) {
result *= i;
}
return result;
}
int main() {
cout << "n " << "e" << endl;
cout << "- " << "-----------" << endl;
printf("0 1\n");
printf("1 2\n");
printf("2 2.5\n");
double e = 2.5;
for (int n = 3; n <= 9; n++) {
double term = 1.0 / factor(n);
e += term;
cout << n << " " << fixed << setprecision(9) << noshowpoint << e <<endl;
}
return 0;
}
代码思路
e
可以通过下面的无穷级数来逼近:
-
定义
factor
函数:这个函数计算阶乘n!
,即从1
到n
的所有整数的乘积。使用一个循环从1
到n
,将每个数乘到result
变量上,最后返回result
。 -
设置输出格式:输出标题行,显示
n
和e
。使用cout
输出分隔线,增加可读性。 -
初始化输出:直接输出
e
的前几项值(当n=0
时e=1
,n=1
时e=2
,n=2
时e=2.5
),这是因为这些值可以直接计算,无需调用factor
函数。 -
计算和输出后续项:使用一个循环从
n=3
到n=9
,每次迭代计算泰勒级数的下一项term
,它是1
除以n!
的值。将term
加到e
的当前值上,以累加泰勒级数的和。使用cout
和格式化指令(fixed
,setprecision
,noshowpoint
)输出n
和当前的e
的近似值。
HDU1013——Digital Roots
题目描述
超时代码
#include <iostream>
using namespace std;
int digit(int num) {
int sum = 0;
while (num > 0) {
sum += num % 10;
num /= 10;
}
if (sum < 10) {
return sum;
}
else {
return digit(sum);
}
}
int main() {
int n;
cin >> n;
while (n != 0) {
cout << digit(n) <<endl;
cin >> n;
}
return 0;
}
改进后依旧超时代码
#include <iostream>
using namespace std;
int digit(int num) {
return 1 + (num - 1) % 9;
}
int main() {
int n;
cin >> n;
while (n != 0) {
cout << digit(n) << endl;
cin >> n;
}
return 0;
}
运行代码
#include <iostream>
#include <string>
using namespace std;
int digit(const string& numStr) {
int num = 0;
for (char c : numStr) {
num += (c - '0');
}
int temp = num % 9;
if (temp == 0) {
return 9;
}
return temp;
}
int main() {
string numStr;
while (cin >> numStr && numStr[0] != '0') {
cout << digit(numStr) << endl;
}
return 0;
}
代码思路
模9的操作实际上是基于数字根(Digital Root)的概念,即一个数的数字根是将其所有数字相加,重复这个过程直到得到一个单数字的结果,这个结果对于任何数来说都是其各位数字相加之和模9的结果(除了全为9的特殊情况)。
-
定义
digit
函数:- 函数接受一个字符串
numStr
作为参数,这个字符串表示一个正整数。 - 通过遍历字符串中的每一个字符(字符代表数字),将字符转换为对应的整数(通过
c - '0'
)并累加到num
变量中。 - 计算
num
模9的结果,如果结果为0,说明num
能被9整除,函数返回9;否则,返回模9的结果。
- 函数接受一个字符串
-
main
函数:- 使用一个无限循环读取标准输入中的字符串,直到读入的第一个字符是'0'为止。
- 在循环内部,每次读取一个字符串
numStr
。 - 调用
digit
函数计算该字符串代表的数的数字根,并输出结果。 - 当读入的字符串以'0'开头时,循环终止,程序结束。