1258:【例9.2】数字金字塔
【题目描述】
观察下面的数字金字塔。写一个程序查找从最高点到底部任意处结束的路径,使路径经过数字的和最大。每一步可以从当前点走到左下方的点也可以到达右下方的点。
在上面的样例中,从13到8到26到15到24的路径产生了最大的和86。
【题目分析】
【代码实现】
#include <bits/stdc++.h>
using namespace std;
int n;
int a[1005][1005];
int maxsum[1005];
int main() {
//input data
cin >> n;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= i; j++) {
cin >> a[i][j];
}
}
//边界条件 最后一行
for (int i = 1; i <= n; i++) {
maxsum[i] = a[n][i];
}
//自底向上算,每一行是一个阶段,最外层循环是阶段循环
//maxsum(i,j)表示i j位置到底边的最大和(状态)
//maxsum(i,j)=max(maxsum(i+1,j),maxsum(i+1,j+1))+a[i][j]
//当前状态数据由当前上一次的最大和与当前数据后面的一个上一次数据计算
//可以使用滚动数组进行空间优化(直接将当前值覆盖上一次数值)
for (int i = n - 1; i >= 1; i--) {
for (int j = 1; j <= i; j++) {
maxsum[j] = max(maxsum[j], maxsum[j + 1]) + a[i][j];
}
}
cout << maxsum[1];
//clock_t s = clock();
//cout <<endl<< clock() - s<<endl;
return 0;
}
1259:【例9.3】求最长不下降序列
【题目描述】
【题目分析】
【代码实现】
#include <bits/stdc++.h>
using namespace std;
int n, a[205];
int b[205];
int pre[205];
//void dfs(int i) {
// if (i == 0) {
// return;
// }
// dfs(pre[i]);
// cout << a[i] << " ";
//}
int main() {
//input data
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
b[i] = 1;
}
int maxn = 0;
int index = 0;
for (int i = 2; i <= n; i++) { //阶段
for (int j = 1; j < i; j++) {
if (a[i] >= a[j] && b[i] < b[j] + 1) {
b[i] = b[j] + 1;
pre[i] = j;
}
}
if (maxn < b[i]) {
maxn = b[i];
index = i;
}
}
cout << "max=" << maxn << endl;
stack<int> sta;
int i = index;
while (i != 0) {
sta.push(a[i]);
i = pre[i];
}
while (!sta.empty()) {
cout << sta.top()<<" ";
sta.pop();
}
return 0;
}
1260:【例9.4】拦截导弹(Noip1999)
【题目描述】
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
输入导弹依次飞来的高度(雷达给出的高度数据是不大于30000的正整数,导弹数不超过1000),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。
【题目分析】
【代码实现】
#include <bits/stdc++.h>
using namespace std;
int a[1005];
int cnt[1005];
int b[1005];
int main() {
//input data
int n = 1;
while (cin >> a[n]) n++;
n--;
//动态规划求最长不上升子序列
cnt[1] = 1;
int maxn = 0;
for (int i = 2; i <= n; i++) {
cnt[i] = 1;
for (int j = 1; j < i; j++) {
if (a[i] <= a[j])
cnt[i] = max(cnt[i], cnt[j] + 1);
}
maxn = max(maxn, cnt[i]);
}
//贪心求最少配备系统数
b[1] = a[1];
int index = 1, minb=0, k=1;
for (int i = 2; i <= n; i++) {
minb = 30000;
for (int j = 1; j <= index; j++) {
if (b[j] >= a[i] && minb > b[j]) {
k = j;
minb = b[j];
}
}
if (minb == 30000) b[++index] = a[i];
else b[k] =a[i];
}
cout << maxn << endl ;
cout << index << endl;
return 0;
}
1261:【例9.5】城市交通路网
【题目描述】
下图表示城市之间的交通路网,线段上的数字表示费用,单向通行由A->E。试用动态规划的最优化原理求出A->E的最省费用。
如图:求v1到v10的最短路径长度及最短路径。
【题目分析】
【代码实现】
#include <bits/stdc++.h>
using namespace std;
const int inf = 0x3f3f3f3f;
int e[505][505];
int rec[505];
int f[505];
int main() {
//input data
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
cin >> e[i][j];
}
f[i] = inf;
}
f[1] = 0; //这一步很关键
for (int i = 2; i <= n; i++) {
for (int j = 1; j < i; j++) {
if (e[j][i] !=0 && f[i] > f[j] + e[j][i]) {
f[i] = f[j] + e[j][i];
rec[i] = j;
}
}
}
cout << "minlong=" << f[n] << endl;
stack<int> sta;
int i=n;
while(i!=0){
sta.push(i);
i=rec[i];
}
while(!sta.empty()){
cout<<sta.top()<<" ";
sta.pop();
}
cout << endl;
return 0;
}
1262:【例9.6】挖地雷
【题目描述】
在一个地图上有n个地窖(n≤200),每个地窖中埋有一定数量的地雷。同时,给出地窖之间的连接路径,并规定路径都是单向的,且保证都是小序号地窖指向大序号地窖,也不存在可以从一个地窖出发经过若干地窖后又回到原来地窖的路径。某人可以从任意一处开始挖地雷,然后沿着指出的连接往下挖(仅能选择一条路径),当无连接时挖地雷工作结束。设计一个挖地雷的方案,使他能挖到最多的地雷。
【题目分析】
【代码实现】
#include <bits/stdc++.h>
using namespace std;
int n, a[205];
bool link[205][205]; //1-表示有连接,0-表示无连接
long long f[205];
int rec[205];
int main() {
//input data
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
f[i] = a[i]; //初始化
}
int x, y;
while (cin >> x, cin >> y, x + y != 0) {
link[x][y] = 1;
}
//f[i]表示到i位置挖到的最多地雷
//动态求解
for (int i = 2; i <= n; i++) {
for (int j = 1; j < i; j++) {
if (link[j][i] && f[i] < f[j] + a[i]) {
f[i] = f[j] + a[i];
rec[i] = j;
}
}
}
//寻找最大值
int index = 0, maxf = 0;
for (int i = 1; i <= n; i++) {
if (maxf < f[i]) {
maxf = f[i];
index = i;
}
}
stack<int> sta;
while (index != 0) {
sta.push(index);
index = rec[index];
}
cout << sta.top();
sta.pop();
while (!sta.empty()) {
cout << "-" << sta.top();
sta.pop();
}
cout << endl << maxf;
return 0;
}
1263:【例9.7】友好城市
【题目描述】
Palmia国有一条横贯东西的大河,河有笔直的南北两岸,岸上各有位置各不相同的N个城市。北岸的每个城市有且仅有一个友好城市在南岸,而且不同城市的友好城市不相同。
每对友好城市都向政府申请在河上开辟一条直线航道连接两个城市,但是由于河上雾太大,政府决定避免任意两条航道交叉,以避免事故。编程帮助政府做出一些批准和拒绝申请的决定,使得在保证任意两条航线不相交的情况下,被批准的申请尽量多。
【题目分析】
【代码实现】
#include <bits/stdc++.h>
using namespace std;
int n;
struct node {
int a;
int b;
} ab[5005];
bool cmp(node &a, node &b) { //从小到大排列
return a.b <= b.b;
}
int f[5005];
int main() {
//input data
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> ab[i].a >> ab[i].b;
f[i] = 1;
}
//sort
sort(ab + 1, ab + n + 1, cmp);
//solution
int ans = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j < i; j++) {
if (ab[i].a >= ab[j].a && f[i] < f[j] + 1) f[i] = f[j] + 1;
}
if (ans < f[i]) ans = f[i];
}
cout << ans;
//clock_t s = clock();
//cout <<endl<< clock() - s<<endl;
return 0;
}
1264:【例9.8】合唱队形
【题目描述】
N位同学站成一排,音乐老师要请其中的(N−K)(位同学出列,使得剩下的K位同学排成合唱队形。
合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1,2,…,K,他们的身高分别为T1,T2,…,TK,则他们的身高满足T1<T2<…<Ti,Ti>Ti+1>…>TK(1≤i≤K)
你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。
【题目分析】
【代码实现】
#include <bits/stdc++.h>
using namespace std;
int n, a[205];
int f[105], g[105];
int main() {
//input data
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
f[i] = 1;
g[i] = 1;
}
for (int i = 2; i <= n; i++) {
for (int j = 1; j < i; j++) {
if (a[i] > a[j])
f[i] = max(f[i], f[j] + 1);
}
}
for (int i = n - 1; i >= 1; i--) {
for (int j = i + 1; j <= n; j++) {
if (a[i] > a[j])
g[i] = max(g[i], g[j] + 1);
}
}
int ans = f[1] + g[1];
for (int i = 1; i <= n; i++) {
ans = max(ans, f[i] + g[i]);
}
cout << n - ans + 1;
return 0;
}
1265:【例9.9】最长公共子序列
【题目描述】
一个给定序列的子序列是在该序列中删去若干元素后得到的序列。确切地说,若给定序列X=<x1,x2,…,xm>,则另一序列Z=<z1,z2,…,zk>是X的子序列是指存在一个严格递增的下标序列<i1,i2,…,ik><,使得对于所有j=1,2,…,k有: Xij=Zj
例如,序列Z=<B,C,D,B>是序列X=<A,B,C,B,D,A,B>的子序列,相应的递增下标序列为<2,3,5,7>。给定两个序列X和Y,当另一序列Z既是X的子序列又是Y的子序列时,称Z是序列X和Y的公共子序列。例如,若X=<A,B,C,B,D,A,B>和Y=<B,D,C,A,B,A>,则序列<B,C,A>是X和Y的一个公共子序列,序列 <B,C,B,A>也是X和Y的一个公共子序列。而且,后者是X和Y的一个最长公共子序列.因为X和Y没有长度大于4的公共子序列。
给定两个序列X=<x1,x2,…,xm>和Y=<y1,y2….yn>.要求找出X和Y的一个最长公共子序列。
【题目分析】
【代码实现】
#include <bits/stdc++.h>
using namespace std;
const int N = 1005;
char a[N], b[N];
int f[N][N];
int main() {
//input data
// string a,b;
// cin>>a>>b;
scanf("%s %s",a,b);
int lena = strlen(a);
int lenb = strlen(b);
// cout << lena << " " << lenb << endl;
for (int i = 1; i <= lena; i++) {
for (int j = 1; j <= lenb; j++) {
f[i][j] = max(f[i - 1][j], f[i][j - 1]);
if (a[i - 1] == b[j - 1]) f[i][j] = max(f[i][j], f[i - 1][j - 1] + 1);
}
}
cout << f[lena][lenb];
return 0;
}
1266:【例9.10】机器分配
【题目描述】
总公司拥有高效设备M台,准备分给下属的N个分公司。各分公司若获得这些设备,可以为国家提供一定的盈利。问:如何分配这M台设备才能使国家得到的盈利最大?求出最大盈利值。其中M≤15,N≤10。分配原则:每个公司有权获得任意数目的设备,但总台数不超过设备数M。
【题目分析】
【代码实现】
#include <bits/stdc++.h>
using namespace std;
int a[15][20], n, m;
int f[20];
int rec[15][20];
int main() {
//input data
cin >> n >> m;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
scanf("%d", &a[i][j]);
//resolve
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
for (int k = j; k >= 0; k--) {
if (f[j] <= f[k] + a[i][j - k]) {
f[j] = f[k] + a[i][j - k];
rec[i][j] = j - k;
}
}
}
}
cout << f[m] << endl;
stack<int> sta;
int i = n, j = m;
while (i >= 1) {
sta.push(rec[i][j]);
j -= rec[i][j];
i--;
}
i = 1;
while (!sta.empty()) {
cout << i << " " << sta.top() << endl;
sta.pop();
i++;
}
return 0;
}
1281:最长上升子序列
【题目描述】
一个数的序列bi,当b1<b2<...<bS的时候,我们称这个序列是上升的。对于给定的一个序列(a1,a2,...,aN),我们可以得到一些上升的子序列(ai1,ai2,...,aiK),这里1≤i1<i2<...<iK≤N。比如,对于序列(1,7,3,5,9,4,8),有它的一些上升子序列,如(1,7),(3,4,8)等等。这些子序列中最长的长度是4,比如子序列(1,3,5,8)。
你的任务,就是对于给定的序列,求出最长上升子序列的长度
【题目分析】
【代码实现】
#include <bits/stdc++.h>
using namespace std;
int n;
int a[1005],f[1005];
int main(){
//input data
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
f[i]=1;
}
for(int i=1;i<=n;i++){
for(int j=1;j<i;j++){
if(a[i]>a[j]&&f[i]<f[j]+1)
f[i]=f[j]+1;
}
}
int ans=0;
for(int i=1;i<=n;i++){
if(ans<f[i]) ans=f[i];
}
cout<<ans;
//clock_t s = clock();
//cout <<endl<< clock() - s<<endl;
return 0;
}
1282:最大子矩阵
【题目描述】
已知矩阵的大小定义为矩阵中所有元素的和。给定一个矩阵,你的任务是找到最大的非空(大小至少是1 × 1)子矩阵。
比如,如下4 × 4的矩阵
的最大子矩阵是
这个子矩阵的大小是15
【题目分析】
枚举二维矩阵起始行和二维矩阵的终止行,求解列和得到一维数组,问题转化为求一维数组的最大连续和
技巧:求解任意多行的列和可以使用前缀和
【代码实现】
#include <bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
int n;
int a[105][105];
int f[105];
int maxsub(int a[], int n) {
int maxsum = -inf;
int d = 0;
for (int i = 1; i <= n; i++) {
if (d > 0)
d += a[i];
else
d = a[i];
if (maxsum < d) maxsum = d;
}
return maxsum;
}
int main() {
//input data
cin >> n;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
cin >> a[i][j];
}
}
int maxans = -inf;
for (int i = 1; i <= n; i++) { //枚举行的起点
memset(f, 0, sizeof(f));
for (int j = i; j <= n; j++) { //枚举行的终点
for (int k = 1; k <= n; k++) { //对列进行求和
f[k] += a[j][k];
}
int m = maxsub(f, n);
if (maxans < m) maxans = m;
}
}
cout << maxans;
//clock_t s = clock();
//cout <<endl<< clock() - s<<endl;
return 0;
}
1283:登山
【题目描述】
五一到了,ACM队组织大家去登山观光,队员们发现山上一共有N个景点,并且决定按照顺序来浏览这些景点,即每次所浏览景点的编号都要大于前一个浏览景点的编号。同时队员们还有另一个登山习惯,就是不连续浏览海拔相同的两个景点,并且一旦开始下山,就不再向上走了。队员们希望在满足上面条件的同时,尽可能多的浏览景点,你能帮他们找出最多可能浏览的景点数么?
【题目分析】
类似于1264:【例9.8】合唱队形
【代码实现】
#include <bits/stdc++.h>
using namespace std;
int n;
int a[1005];
int lenup[1005];
int lendon[1005];
int main() {
//input data
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
lenup[i] = 1;
lendon[i] = 1;
}
int ans = 0;
for (int i = 1; i <= n; i++) { //海拔从低到高
for (int j = 1; j < i; j++) {
if (a[i] > a[j] && lenup[i] < lenup[j] + 1)
lenup[i] = lenup[j] + 1;
}
}
for (int i = n; i >= 1; i--) { //海拔从低到高
for (int j = n; j > i; j--) {
if (a[i] > a[j] && lendon[i] < lendon[j] + 1)
lendon[i] = lendon[j] + 1;
}
}
for (int i = 1; i <= n; i++) {
ans = max(ans, lendon[i] + lenup[i] - 1);
}
cout << ans;
return 0;
}
1284:摘花生
【题目描述】
Hello Kitty想摘点花生送给她喜欢的米老鼠。她来到一片有网格状道路的矩形花生地(如下图),从西北角进去,东南角出来。地里每个道路的交叉点上都有种着一株花生苗,上面有若干颗花生,经过一株花生苗就能摘走该它上面所有的花生。Hello Kitty只能向东或向南走,不能向西或向北走。问Hello Kitty最多能够摘到多少颗花生。
【题目分析】
【代码实现】
#include <bits/stdc++.h>
using namespace std;
int n;
int a[105][105];
int f[105][105];
int main(){
//input data
cin>>n;
for(int i=1;i<=n;i++){
int p,q;
cin>>p>>q;
for(int j=1;j<=p;j++){
for(int k=1;k<=q;k++){
cin>>a[j][k]; //可以使用两个一维数组进行滚动求解
a[j][k]=max(a[j-1][k],a[j][k-1])+a[j][k];
}
}
cout<<a[p][q]<<endl;
}
return 0;
}
1285:最大上升子序列和
【题目描述】
【题目分析】
【代码实现】
#include <bits/stdc++.h>
using namespace std;
int n,a[1005],f[1005];
int main(){
//input data
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
f[i]=a[i];
}
int maxf=f[1];
for(int i=2;i<=n;i++){
for(int j=1;j<i;j++){
if(a[i]>a[j]) f[i]=max(f[i],f[j]+a[i]);
}
maxf=max(maxf,f[i]);
}
cout<<maxf<<endl;
return 0;
}
1286:怪盗基德的滑翔翼
【题目描述】
怪盗基德是一个充满传奇色彩的怪盗,专门以珠宝为目标的超级盗窃犯。而他最为突出的地方,就是他每次都能逃脱中村警部的重重围堵,而这也很大程度上是多亏了他随身携带的便于操作的滑翔翼。
有一天,怪盗基德像往常一样偷走了一颗珍贵的钻石,不料却被柯南小朋友识破了伪装,而他的滑翔翼的动力装置也被柯南踢出的足球破坏了。不得已,怪盗基德只能操作受损的滑翔翼逃脱。
假设城市中一共有N幢建筑排成一条线,每幢建筑的高度各不相同。初始时,怪盗基德可以在任何一幢建筑的顶端。他可以选择一个方向逃跑,但是不能中途改变方向(因为中森警部会在后面追击)。因为滑翔翼动力装置受损,他只能往下滑行(即:只能从较高的建筑滑翔到较低的建筑)。他希望尽可能多地经过不同建筑的顶部,这样可以减缓下降时的冲击力,减少受伤的可能性。请问,他最多可以经过多少幢不同建筑的顶部(包含初始时的建筑)?
【题目分析】
分别求最长上升子序列和最长下降子序列,求两者的最大值
【代码实现】
#include <bits/stdc++.h>
using namespace std;
int n, a[105], len[105];
int main() {
//input data
cin >> n;
for (int i = 1; i <= n; i++) {
int m;
cin >> m;
int ans = 0;
for (int j = 1; j <= m; j++) {
cin >> a[j];
len[j] = 1;
for (int k = 1; k < j; k++) {
if (a[j] < a[k] && len[j] < len[k] + 1)
len[j] = len[k] + 1;
}
if (ans < len[j]) ans = len[j];
}
for (int j = m; j >= 1; j--) {
len[j] = 1;
for (int k = m; k > j; k--) {
if (a[j] < a[k] && len[j] < len[k] + 1)
len[j] = len[k] + 1;
}
if (ans < len[j])ans = len[j];
}
cout<<ans<<endl;
}
return 0;
}
1287:最低通行费
【题目描述】
一个商人穿过一个N×N的正方形的网格,去参加一个非常重要的商务活动。他要从网格的左上角进,右下角出。每穿越中间1个小方格,都要花费1个单位时间。商人必须在(2N-1)个单位时间穿越出去。而在经过中间的每个小方格时,都需要缴纳一定的费用。
这个商人期望在规定时间内用最少费用穿越出去。请问至少需要多少费用?
注意:不能对角穿越各个小方格(即,只能向上下左右四个方向移动且不能离开网格)。
【题目分析】
因为本题有2n-1个方格的限制,所有只能向下走和向右走与1284题目类似
【代码实现】
#include <bits/stdc++.h>
using namespace std;
int a[1005][1005];
int f[1005][1005];
int main() {
//input data
int n;
cin >> n;
for (int j = 1; j <= n; j++) {
for (int k = 1; k <= n; k++) {
cin >> a[j][k]; //可以使用两个一维数组进行滚动求解
if (j == 1) a[j][k] += a[j][k - 1];
else if (k == 1) a[j][k] += a[j - 1][k];
else
a[j][k] = min(a[j - 1][k], a[j][k - 1]) + a[j][k];
}
}
cout << a[n][n] << endl;
return 0;
}
1288:三角形最佳路径问题
【题目描述】
如下所示的由正整数数字构成的三角形:
从三角形的顶部到底部有很多条不同的路径。对于每条路径,把路径上面的数加起来可以得到一个和,和最大的路径称为最佳路径。你的任务就是求出最佳路径上的数字之和。
注意:路径上的每一步只能从一个数走到下一层上和它最近的下边(正下方)的数或者右边(右下方)的数
【题目分析】
与1258 数字金字塔 类似
【代码实现】
#include <bits/stdc++.h>
using namespace std;
int n;
int sum[105];
int a[105][105];
int main() {
//input data
cin >> n;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= i; j++) {
cin >> a[i][j];
}
}
for (int i = n; i >= 1; i--) {
for (int j = 1; j <= i; j++) {
sum[j] = max(sum[j], sum[j + 1]) + a[i][j];
}
}
cout << sum[1];
//clock_t s = clock();
//cout <<endl<< clock() - s<<endl;
return 0;
}
1289:拦截导弹
【题目描述】
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。
输入导弹依次飞来的高度(雷达给出的高度数据是不大于30000的正整数),计算这套系统最多能拦截多少导弹。
【题目分析】
求最大不上升子序列的长度
【代码实现】
#include <bits/stdc++.h>
using namespace std;
int n;
int a[20];
int f[20];
int main() {
//input data
cin >> n;
int ans = 0;
for (int i = 1; i <= n; i++) {
cin >> a[i];
f[i] = 1;
for (int j = 1; j < i; j++) {
if (a[i] < a[j] && f[i] < f[j] + 1)
f[i] = f[j] + 1;
}
if (ans < f[i]) ans = f[i];
}
cout << ans;
return 0;
}