SSL 1664 取数字问题
题目描述:
给定M*N的矩阵,其中的每个元素都是-10到10之间的整数。你的任务是从左上角(1,1)走到右下角(M,N),每一步只能向右或向下,并且不能走出矩阵的范围。你所经过的方格里面的数字都必须被选取,请找出一条最合适的道路,使得在路上被选取的数字之和是尽可能小的正整数。
输入格式:
第一行两个整数M,N,(2<=M,N<=10),分别表示矩阵的行和列的数目。
接下来的M行,每行包括N个整数,就是矩阵中的每一行的N个元素。
输出格式
仅一行一个整数,表示所选道路上数字之和所能达到的最小的正整数。如果不能达到任何正整数就输出-1。
样例输入
2 2
0 2
1 0
样例输出
1
解题思路:
此题可以考虑DP和记搜两种做法。
- 记搜做法:
因为需要找一个最小的正整数,所以我们可以从 1... n ∗ m ∗ 10 1...n*m*10 1...n∗m∗10 的正整数集合中从小到大来判定是否合法,因此问题就转换成了判定性问题:给一个正整数 x x x ,是否存在一条路径的和为 x x x。
设
f
i
,
j
,
k
f_{i,j,k}
fi,j,k 为从
(
1
,
1
)
(1,1)
(1,1) 到
(
i
,
j
)
(i,j)
(i,j) ,是否存在一条路径和为
k
k
k ,存在为
1
1
1,否之为
0
0
0。
我们假设存在一条路径,使得从
(
1
,
1
)
(1,1)
(1,1) 到达
(
n
,
m
)
(n,m)
(n,m) 后路径和为
i
i
i,则如果不加上
(
n
,
m
)
(n,m)
(n,m) 的点权的路径和为
i
−
a
n
,
m
i-a_{n,m}
i−an,m,因此可见,
f
n
,
m
,
i
−
a
n
,
m
=
1
f_{n,m,i-a_{n,m}}=1
fn,m,i−an,m=1,由此推知,
f
n
−
1
,
m
,
i
−
a
n
,
m
−
a
n
−
1
,
m
f_{n-1,m,i-a_{n,m}-a_{n-1,m}}
fn−1,m,i−an,m−an−1,m 以及
f
n
,
m
−
1
,
i
−
a
n
,
m
−
a
n
,
m
−
1
f_{n,m-1,i-a_{n,m}-a_{n,m-1}}
fn,m−1,i−an,m−an,m−1 都必然存在(因为
f
n
,
m
f_{n,m}
fn,m 是由
f
n
−
1
,
m
f_{n-1,m}
fn−1,m 和
f
n
,
m
−
1
f_{n,m-1}
fn,m−1 转移的),因此,若存在这条路径,则
f
1
,
1
,
0
f_{1,1,0}
f1,1,0也必然存在
因此,我们只需要用记搜从 ( n , m ) (n,m) (n,m) 一步一步往 ( 1 , 1 ) (1,1) (1,1) 推,若最后 f 1 , 1 , 0 = 1 f_{1,1,0}=1 f1,1,0=1,则表明这条路径和为 i i i 的路径存在。
#include <iostream>
#include <cstring>
using namespace std;
int n,m,map[20][20],M=1001;
bool f[20][20][100010]={false};
void dfs(int x,int y,int d)
{
f[x][y][d+M]=1; //由于数组不能表示负数,所以我们手动加上一个M,此时0~M-1表示负数,M表示0,M+1~ M*2表示正数
if(f[1][1][M]) return ;
if((x>1)&&(!f[x-1][y][d-map[x-1][y]+M])) dfs(x-1,y,d-map[x-1][y]);
if((y>1)&&(!f[x][y-1][d-map[x][y-1]+M])) dfs(x,y-1,d-map[x][y-1]);
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
cin>>map[i][j];
for(int i=1;i<=n*m*10;i++) //枚举答案判定
{
dfs(n,m,i-map[n][m]);
if(f[1][1][M]) //存在一条路径
{
cout<<i;
return 0;
}
}
cout<<-1;
return 0;
}
- DP做法:
解题思路和记搜类似, 因为知道了 f i , j − 1 , k f_{i,j-1,k} fi,j−1,k ,或 f i − 1 , j , k f_{i-1,j,k} fi−1,j,k 存在,因此 f i , j , k + a i , j f_{i,j,k+a_{i,j}} fi,j,k+ai,j 也必然存在。从 ( 1 , 1 ) (1,1) (1,1) 一直推至 ( n , m ) (n,m) (n,m),然后选一个最小的 k k k 使得 f n , m , k = 1 f_{n,m,k}=1 fn,m,k=1。
#include <iostream>
using namespace std;
int n,m,map[20][20],M=1001;
bool f[20][20][100010]={false};
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
cin>>map[i][j];
f[1][1][map[1][1]+M]=true; //由于数组不能表示负数,所以我们手动加上一个M,此时0~M-1表示负数,M表示0,M+1~ M*2表示正数
int t=map[1][1]+M;
for(int i=2;i<=n;i++)
f[i][1][map[i][1]+t]=true,t+=map[i][1]; //由于第一列的f[i][j]只能从f[i-1][j]推知,因此预处理
t=map[1][1]+M;
for(int i=2;i<=m;i++)
f[1][i][map[1][i]+t]=true,t+=map[1][i]; //由于第一行的f[i][j]只能从f[i][j-1]推知,因此预处理
for(int i=2;i<=n;i++)
{
for(int j=2;j<=m;j++)
{
for(int k=1;k<=2*M;k++)
if(f[i][j-1][k]) f[i][j][k+map[i][j]]=true; //考虑第一种转移
for(int k=1;k<=M*2;k++)
if(f[i-1][j][k]) f[i][j][k+map[i][j]]=true; //考虑第二种转移
}
}
for(int i=M+1;i<=M*2;i++)
if(f[n][m][i])
{
cout<<i-M;
return 0;
}
cout<<-1;
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!