动态规划
A
Problem A. 晴天小猪历险记之Hill
时间限制 1000 ms
内存限制 128 MB
题目描述
这一天,他来到了一座深山的山脚下,因为只有这座深山中的一位隐者才知道这种药草的所在。但是上山的路错综复杂,由于小小猪的病情,晴天小猪想找一条需时最少的路到达山顶,但现在它一头雾水,所以向你求助。
山用一个三角形表示,从山顶依次向下有1段、2段、3段等山路,每一段用一个数字T(1< =T< =100)表示,代表晴天小猪在这一段山路上需要爬的时间,每一次它都可以朝左、右、左上、右上四个方向走(注意:在任意一层的第一段也可以走到本层的最后一段或上一层的最后一段)。
晴天小猪从山的左下角出发,目的地为山顶,即隐者的小屋。
输入数据
第一行有一个数 n (2≤n≤1000),n (2≤n≤1000), 表示山的高度。
从第二行至第 n+1n+1 行,第 i+1i+1 行有 ii 个数,每个数表示晴天小猪在这一段山路上需要爬的时间。
输出数据
一个数,即晴天小猪所需要的最短时间。
样例输入
5
1
2 3
4 5 6
10 1 7 8
1 1 4 5 6
样例输出
10
样例说明
在山的两侧的走法略有特殊,请自己模拟一下,开始我自己都弄错了……
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <algorithm>
#define MaxSize 1050
using namespace std;
const int MaxNum = 987654321;
int n, mountain[MaxSize][MaxSize], dp[MaxSize][MaxSize];
int sum(int x, int y)
{
return x + y;
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= i; j++)
{
scanf("%d", &mountain[i][j]);
}
}
for (int i = 0; i <= n + 1; i++)
{
for (int j = 0; j <= 1010; j++)
{
dp[i][j] = MaxNum;
}
}
dp[n][1] = 0;
for (int i = n; i >= 1; i--)
{
for (int j = 1; j <= i; j++)
{
if (j == 1)
dp[i][j] = min(min(dp[i][j], dp[i + 1][j] + mountain[i + 1][j]), min(dp[i + 1][i + 1] + mountain[i + 1][i + 1], dp[i + 1][j + 1] + mountain[i + 1][j + 1]));
else if (j == i)
dp[i][j] = min(dp[i][j], min(dp[i + 1][1] + mountain[i + 1][1], min(dp[i + 1][j + 1] + mountain[i + 1][j + 1], dp[i + 1][j] + mountain[i + 1][j])));
else
dp[i][j] = min(dp[i][j], min(dp[i + 1][j + 1] + mountain[i + 1][j + 1], dp[i + 1][j] + mountain[i + 1][j]));
}
dp[i][1] = min(dp[i][1], dp[i][i] + mountain[i][i]);
for (int j = 2; j <= i; j++)
dp[i][j] = min(dp[i][j], dp[i][j - 1] + mountain[i][j - 1]);
dp[i][n] = min(dp[i][n], dp[i][1] + mountain[i][1]);
for (int j = n - 1; j >= 1; j--)
dp[i][j] = min(dp[i][j], dp[i][j + 1] + mountain[i][j + 1]);
}
cout << sum(dp[1][1],mountain[1][1]);
return 0;
}
B
Problem B. 清帝之惑之顺治
时间限制 1000 ms
内存限制 128 MB
题目描述
顺治喜欢滑雪,这并不奇怪, 因为滑雪的确很刺激。可是为了获得速度,滑的区域必须向下倾斜,而且当你滑到坡底,你不得不再次走上坡或者等待太监们来载你。顺治想知道载一个区域中最长的滑坡。
区域由一个二维数组给出。数组的每个数字代表点的高度。下面是一个例子:
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更长。事实上,这是最长的一条。
输入数据
输入的第一行表示区域的行数 RR 和列数 C (1≤R,C≤500)C (1≤R,C≤500) 。下面是 RR 行,每行有 CC 个整数,代表高度 h,0≤h<103h,0≤h<103 。
输出数据
输出最长区域的长度。
样例输入
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
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <algorithm>
using namespace std;
int high[550][550], dp[550][550];
int xx, yy, max_ans;
bool inmap(int x, int y)
{
return x >= 0 && x < xx&& y >= 0 && y < yy;
}
int solve(int x, int y)
{
if (dp[x][y])
return dp[x][y];
dp[x][y] = 1;
int xu = x, yu = y - 1, xd = x, yd = y + 1, xr = x + 1, yr = y, xl = x - 1, yl = y;
if (inmap(xu, yu) && high[xu][yu] > high[x][y])
{
dp[x][y] = max(dp[x][y], solve(xu, yu)+1);
}
if (inmap(xd, yd) && high[xd][yd] > high[x][y])
{
dp[x][y] = max(dp[x][y], solve(xd, yd)+1);
}
if (inmap(xl, yl) && high[xl][yl] > high[x][y])
{
dp[x][y] = max(dp[x][y], solve(xl, yl)+1);
}
if (inmap(xr, yr) && high[xr][yr] > high[x][y])
{
dp[x][y] = max(dp[x][y], solve(xr, yr)+1);
}
return dp[x][y];
}
int main()
{
cin >> xx >> yy;
for (int i = 0; i < xx; i++)
for (int j = 0; j < yy; j++) {
cin >> high[i][j];
}
max_ans = 0;
for (int i = 0; i < xx; i++)
for (int j = 0; j < yy; j++)
{
int tmp = solve(i, j);
if (tmp > max_ans)
max_ans = tmp;
}
cout << max_ans;
return 0;
}
C
Problem C. 积木城堡
时间限制 1000 ms
内存限制 128 MB
题目描述
XC的儿子小XC最喜欢玩的游戏用积木垒漂亮的城堡。城堡是用一些立方体的积木垒成的,城堡的每一层是一块积木。小XC是一个比他爸爸XC还聪明的孩子,他发现垒城堡的时候,如果下面的积木比上面的积木大,那么城堡便不容易倒。所以他在垒城堡的时候总是遵循这样的规则。
小XC想把自己垒的城堡送给幼儿园里漂亮的女孩子们,这样可以增加他的好感度。为了公平起见,他决定把送给每个女孩子一样高的城堡,这样可以避免女孩子们为了获得更漂亮的城堡而引起争执。可是他发现自己在垒城堡的时候并没有预先考虑到这一点。所以他现在要改造城堡。由于他没有多余的积木了,他灵机一动,想出了一个巧妙的改造方案。他决定从每一个城堡中挪去一些积木,使得最终每座城堡都一样高。为了使他的城堡更雄伟,他觉得应该使最后的城堡都尽可能的高。
任务:
请你帮助小XC编一个程序,根据他垒的所有城堡的信息,决定应该移去哪些积木才能获得最佳的效果。
输入数据
第一行是一个整数 N (N≤100),N (N≤100), 表示一共有几座城堡。以下 NN 行每行是一系列非负整数,用一个空格分隔,按从下往上的顺序依次给出一座城堡中所有积木的棱长。用-1
结束。一座城堡中的积木不超过100块,每块积木的棱长不超过100。
输出数据
一个整数,表示最后城堡的最大可能的高度。如果找不到合适的方案,则输出 00 。
样例输入
2
2 1 –1
3 2 1 –1
样例输出
3
样例说明
原数据有误,不知我修正后是不是对?
#include <stdio.h>
#define MaxSize 105
int a[MaxSize], dp[MaxSize][MaxSize * MaxSize], n;
int solve(int v)
{
for (int i = 0; i < n; i++)
if (dp[i][v] == 0)
return 0;
return 1;
}
int main()
{
int ans;
scanf("%d", &n);
for (int i = 0; i < n; i++)
{
int cnt = 0, sum = 0;
while (1)
{
scanf("%d", &a[cnt]);
if (a[cnt] == -1)
{
break;
}
sum = sum + a[cnt];
cnt++;
}
dp[i][0] = 1;
for (int j = 0; j < cnt; j++)
for (int k = sum; k >= a[j]; k--)
if (dp[i][k - a[j]])
{
dp[i][k] = 1;
}
}
ans = 10001;
while (true)
{
if (dp[0][ans] != 0)
break;
ans--;
}
while (true)
{
if (solve(ans) != 0)
break;
ans--;
}
printf("%d\n", ans);
return 0;
}
D
Problem D. Warcraft III 守望者的烦恼
时间限制 1000 ms
内存限制 128 MB
题目描述
头脑并不发达的warden最近在思考一个问题,她的闪烁技能是可以升级的,k级的闪烁技能最多可以向前移动k个监狱,一共有n个监狱要视察,她从入口进去,一路上有n个监狱,而且不会往回走,当然她并不用每个监狱都视察,但是她最后一定要到第n个监狱里去,因为监狱的出口在那里,但是她并不一定要到第1个监狱。
守望者warden现在想知道,她在拥有k级闪烁技能时视察n个监狱一共有多少种方案?
输入数据
第一行是闪烁技能的等级 k (1≤k≤10)k (1≤k≤10)
第二行是监狱的个数 n (1≤n≤231−1)n (1≤n≤231−1)
输出数据
由于方案个数会很多,所以输出它 mod 7777777后的结果就行了
样例输入
2
4
样例输出
5
样例说明
把监狱编号1 2 3 4,闪烁技能为2级,
一共有5种方案
→1→2→3→4
→2→3→4
→2→4
→1→3→4
→1→2→4
小提示:建议用int64,否则可能会溢出
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <cstring>
#include <algorithm>
#define ll long long
#define mod 7777777
using namespace std;
struct matrix
{
ll s[15][15];
} a, b;
int n, k;
matrix Matrix(matrix x, matrix y)
{
matrix c;
memset(c.s, 0, sizeof(c.s));
for (int i = 1; i <= k; i++)
for (int j = 1; j <= k; j++)
for (int kk = 1; kk <= k; kk++)
{
c.s[i][j] += x.s[i][kk] * y.s[kk][j];
c.s[i][j] %= mod;
}
return c;
}
void init_()
{
cin >> k >> n;
memset(a.s, 0, sizeof(a.s));
memset(b.s, 0, sizeof(b.s));
}
int main()
{
init_();
a.s[k][k] = 1; //初始化,dp[1]=1
for (int i = 1; i < k; i++)
b.s[i][i + 1] = 1;
for (int i = 1; i <= k; i++)
b.s[k][i] = 1;
while (n)
{ //dp[n]需要乘以n个矩阵b
if (n & 1)
a = Matrix(a, b);
b = Matrix(b, b);
n >>= 1;
}
printf("%lld", a.s[k][k]);
return 0;
}
E
Problem E. 加分二叉树
时间限制 1000 ms
内存限制 128 MB
题目描述
设一个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的前序遍历
输入数据
第 11 行:一个整数 n (n<30),n (n<30), 为节点个数。
第 22 行 :n:n 个用空格隔开的整数,为每个节点的分数(分数 <100)<100) 。
输出数据
第 11 行:一个整数,为最高加分(结果不会超过 4,000,000,000)4,000,000,000) 。
第 22 行 :n:n 个用空格隔开的整数,为该树的前序遍历。
若存在多种前序遍历均为最高加分,则输出字典序最小的前序遍历
样例输入
5
5 7 1 2 10
样例输出
145
3 1 2 4 5
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <algorithm>
using namespace std;
#define ll long long
int n, a[40], root[40][40];
ll dp[40][40];
ll dfs(int L, int R)
{
if (L > R)
return 1; //因为要左右子节点相乘,所以如果是空则给1
if (dp[L][R])
return dp[L][R]; //关键一步,记忆化,以前走过那就直接返回结果
ll maxn = 0;
for (int i = L; i < R; i++)
{
ll t = dfs(L, i - 1) * dfs(i + 1, R) + a[i];
if (t > maxn)
{
maxn = t;
root[L][R] = i;
}
}
return dp[L][R] = maxn;
}
void dg(int L, int R)
{
if (L > R)
return; //防止出现 L >R
printf("%d ", root[L][R]);
dg(L, root[L][R] - 1);
dg(root[L][R] + 1, R);
}
void init_()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
dp[i][i] = a[i];
root[i][i] = i;
}
}
int main()
{
init_();
printf("%lld\n", dfs(1, n));
dg(1, n);
return 0;
}