数字三角形模型
1015. 摘花生
Hello Kitty想摘点花生送给她喜欢的米老鼠。
她来到一片有网格状道路的矩形花生地(如下图),从西北角进去,东南角出来。
地里每个道路的交叉点上都有种着一株花生苗,上面有若干颗花生,经过一株花生苗就能摘走该它上面所有的花生。
Hello Kitty只能向东或向南走,不能向西或向北走。
问Hello Kitty最多能够摘到多少颗花生。
输入格式
第一行是一个整数T,代表一共有多少组数据。
接下来是T组数据。
每组数据的第一行是两个整数,分别代表花生苗的行数R和列数 C。
每组数据的接下来R行数据,从北向南依次描述每行花生苗的情况。每行数据有C个整数,按从西向东的顺序描述了该行每株花生苗上的花生数目M。
输出格式
对每组输入数据,输出一行,内容为Hello Kitty能摘到得最多的花生颗数。
数据范围
\(1≤T≤100, 1≤R,C≤100, 0≤M≤1000\)
输入样例:
2
2 2
1 1
3 4
2 3
2 3 4
1 6 5
输出样例:
8
16
思路:数字三角形模型最朴素的一种题:
闫氏思考法:
#include<iostream>
#include<string.h>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<queue>
#include<vector>
#include<string>
#include<set>
#include<map>
using namespace std;
typedef pair<int,int> PII;
typedef long long LL;
const int N=110;
int a[N][N],dp[N][N];
int main(){
int n,m,T;
cin>>T;
while(T--){
cin>>n>>m;
for(int i=1;i<=n;++i){
for(int j=1;j<=m;++j)
cin>>a[i][j];
}
memset(dp,0,sizeof dp);
for(int i=1;i<=n;++i){
for(int j=1;j<=m;++j){
dp[i][j]=max(dp[i-1][j],dp[i][j-1])+a[i][j];
}
}
cout<<dp[n][m]<<endl;
}
return 0;
}
1018. 最低通行费
思路:同上一题,注意不要取到边界
#include<iostream>
#include<string.h>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<queue>
#include<vector>
#include<string>
#include<set>
#include<map>
using namespace std;
typedef pair<int,int> PII;
typedef long long LL;
const int N=110;
int a[N][N],dp[N][N];
int main(){
int n,m,T;
cin>>n;
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j)
cin>>a[i][j];
}
for(int i=2;i<=n;++i){
dp[i][0]=0x3f3f3f3f;
dp[0][i]=0x3f3f3f3f;
}
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j){
dp[i][j]=min(dp[i-1][j],dp[i][j-1])+a[i][j];
}
}
cout<<dp[n][n]<<endl;
return 0;
}
方格取数
设有 N×N 的方格图,我们在其中的某些方格中填入正整数,而其它的方格中则放入数字0。如下图所示:
某人从图中的左上角 A 出发,可以向下行走,也可以向右行走,直到到达右下角的 B 点
在走过的路上,他可以取走方格中的数(取走后的方格中将变为数字0)。
此人从 A 点到 B 点共走了两次,试找出两条这样的路径,使得取得的数字和为最大。
输入格式
第一行为一个整数N,表示 N×N 的方格图。
接下来的每行有三个整数,第一个为行号数,第二个为列号数,第三个为在该行、该列上所放的数。
一行“0 0 0”表示结束。
输出格式
输出一个整数,表示两条路径上取得的最大的和。
数据范围
N≤10
输入样例:
8
2 3 13
2 6 6
3 5 7
4 4 14
5 2 21
5 6 4
6 3 15
7 2 14
0 0 0
输出样例:
67
思路:
可以判断两次路线是否走到同一格子来消除重复,只有在同一时间才有可能走到同一个格子即位置i+j相等,我们定义状态的一个值时k为时间,i1表示第一次的行坐标,i2表示第二次的行坐标,通过k-i1,k-i2就可以分别得到列坐标了。
#include<iostream>
#include<string.h>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<queue>
#include<vector>
#include<string>
#include<set>
#include<map>
using namespace std;
typedef pair<int,int> PII;
typedef long long LL;
const int N=15;
int a[N][N],dp[N*2][N][N],n;
int main(){
cin>>n;
int x,y,z;
while(cin>>x>>y>>z&&(x||y||z))
a[x][y]=z;
for(int k=2;k<=2*n;++k){
for(int i1=1;i1<=n;++i1){
for(int i2=1;i2<=n;++i2){
int j1=k-i1,j2=k-i2;
if(j1<=0||j1>n||j2<=0||j2>n) continue;
int t=a[i1][j1];
if(i1!=i2){
t+=a[i2][j2];
}
dp[k][i1][i2]=max(dp[k-1][i1-1][i2]+t,dp[k][i1][i2]);
dp[k][i1][i2]=max(dp[k-1][i1][i2-1]+t,dp[k][i1][i2]);
dp[k][i1][i2]=max(dp[k-1][i1-1][i2-1]+t,dp[k][i1][i2]);
dp[k][i1][i2]=max(dp[k-1][i1][i2]+t,dp[k][i1][i2]);
}
}
}
cout<<dp[2*n][n][n]<<endl;
return 0;
}
传纸条
小渊和小轩是好朋友也是同班同学,他们在一起总有谈不完的话题。
一次素质拓展活动中,班上同学安排坐成一个 m 行 n 列的矩阵,而小渊和小轩被安排在矩阵对角线的两端,因此,他们就无法直接交谈了。
幸运的是,他们可以通过传纸条来进行交流。
纸条要经由许多同学传到对方手里,小渊坐在矩阵的左上角,坐标(1,1),小轩坐在矩阵的右下角,坐标(m,n)。
从小渊传到小轩的纸条只可以向下或者向右传递,从小轩传给小渊的纸条只可以向上或者向左传递。
在活动进行中,小渊希望给小轩传递一张纸条,同时希望小轩给他回复。
班里每个同学都可以帮他们传递,但只会帮他们一次,也就是说如果此人在小渊递给小轩纸条的时候帮忙,那么在小轩递给小渊的时候就不会再帮忙,反之亦然。
还有一件事情需要注意,全班每个同学愿意帮忙的好感度有高有低(注意:小渊和小轩的好心程度没有定义,输入时用0表示),可以用一个0-100的自然数来表示,数越大表示越好心。
小渊和小轩希望尽可能找好心程度高的同学来帮忙传纸条,即找到来回两条传递路径,使得这两条路径上同学的好心程度之和最大。
现在,请你帮助小渊和小轩找到这样的两条路径。
输入格式
第一行有2个用空格隔开的整数 m 和 n,表示学生矩阵有 m 行 n 列。
接下来的 m 行是一个 m∗n 的矩阵,矩阵中第 i 行 j 列的整数表示坐在第 i 行 j 列的学生的好心程度,每行的 n 个整数之间用空格隔开。
输出格式
输出一个整数,表示来回两条路上参与传递纸条的学生的好心程度之和的最大值。
数据范围
1≤n,m≤50
输入样例:
3 3
0 3 9
2 8 5
5 7 0
输出样例:
34
思路:
从右下角到左上角的移动完全可以看作逆过程,那就变成了类似上一题两次移动取物不取重复,不过还要求不能走到同一格,即两次路线不能有交集。由于起点和终点相同,必定有一次移动在上,另一次移动在上,所以只要枚举第二次移动的行在第一次移动的行下面的情况即可。还需要注意在第一次移动只有在最后一步才能进入第N行,所以dp[k][n][n]可以留到最后特判。
#include<bits/stdc++.h>
using namespace std;
const int N=60;
int a[N][N],dp[N+N][N][N];
int main(){
int n,m;
cin>>n>>m;
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
cin>>a[i][j];
for(int k=2;k<=n+m;++k){
for(int i1=1;i1<=n-1;++i1){
for(int i2=i1+1;i2<=n;++i2){
int j1=k-i1,j2=k-i2;
if(j1<=0||j1>m||j2<=0||j2>m) continue;
int& v=dp[k][i1][i2];
int t1=a[i1][j1],t2=a[i2][j2];
v=max(dp[k-1][i1-1][i2]+t1+t2,v);
v=max(dp[k-1][i1][i2-1]+t1+t2,v);
v=max(dp[k-1][i1][i2]+t1+t2,v);
v=max(dp[k-1][i1-1][i2-1]+t1+t2,v);
}
}
}
int& v=dp[n+m][n][n];
int k=n+m;
v=max(dp[k-1][n-1][n],v);
v=max(dp[k-1][n][n-1],v);
v=max(dp[k-1][n][n],v);
v=max(dp[k-1][n-1][n-1],v);
cout<<dp[n+m][n][n]<<endl;
return 0;
}
补充题:
免费馅饼
Problem Description
都说天上不会掉馅饼,但有一天gameboy正走在回家的小径上,忽然天上掉下大把大把的馅饼。说来gameboy的人品实在是太好了,这馅饼别处都不掉,就掉落在他身旁的10米范围内。馅饼如果掉在了地上当然就不能吃了,所以gameboy马上卸下身上的背包去接。但由于小径两侧都不能站人,所以他只能在小径上接。由于gameboy平时老呆在房间里玩游戏,虽然在游戏中是个身手敏捷的高手,但在现实中运动神经特别迟钝,每秒种只有在移动不超过一米的范围内接住坠落的馅饼。现在给这条小径如图标上坐标
为了使问题简化,假设在接下来的一段时间里,馅饼都掉落在0-10这11个位置。开始时gameboy站在5这个位置,因此在第一秒,他只能接到4,5,6这三个位置中其中一个位置上的馅饼。问gameboy最多可能接到多少个馅饼?(假设他的背包可以容纳无穷多个馅饼)
Input
输入数据有多组。每组数据的第一行为以正整数n(0<n<100000),表示有n个馅饼掉在这条小径上。在结下来的n行中,每行有两个整数x,T(0<T<100000),表示在第T秒有一个馅饼掉在x点上。同一秒钟在同一点上可能掉下多个馅饼。n=0时输入结束。
Output
每一组输入数据对应一行输出。输出一个整数m,表示gameboy最多可能接到m个馅饼。
提示:本题的输入数据量比较大,建议用scanf读入,用cin可能会超时。
Sample Input
6
5 1
4 1
6 1
7 2
7 2
8 3
0
Sample Output
4
思路:
从某一顶点开始,每一个决策向左或向右,显然是一个数字三角形模型。然后考虑dp的顺序,由于时间小于100000,也就是有100000层数塔,每层11个位置。所以可以先枚举时间,再枚举位置。
#include<bits/stdc++.h>
using namespace std;
const int N=100010,M=15;
typedef pair<int,int> PII;
int dp[N][M],a[N][M];
int main(){
int n;
while(scanf("%d",&n)!=EOF&&n){
int maxt=0;
memset(a,0,sizeof a);
memset(dp,0,sizeof dp);
for(int i=1,x,t;i<=n;++i){
scanf("%d%d",&x,&t);
a[t][x]++;
maxt=max(maxt,t);
}
for(int i=maxt;i>=0;--i){
dp[i][0]=max(dp[i+1][0],dp[i+1][1])+a[i][0];
for(int j=1;j<=9;++j){
int &v=dp[i][j];
v=max(v,dp[i+1][j-1]+a[i][j]);
v=max(v,dp[i+1][j]+a[i][j]);
v=max(v,dp[i+1][j+1]+a[i][j]);
}
dp[i][10]=max(dp[i+1][10],dp[i+1][9])+a[i][10];
}
cout<<dp[0][5]<<endl;
}
return 0;
}