【题解】洛谷P1373 小a和uim之大逃离(dp 递推)

本文介绍了一道关于雨林探险的动态规划题目,玩家需要帮助小a和uim找到尽可能多的生存策略。文章详细解析了两种解题思路:暴力搜索与动态规划,并给出了完整的代码实现。

题目背景

小a和uim来到雨林中探险。突然一阵北风吹来,一片乌云从北部天边急涌过来,还伴着一道道闪电,一阵阵雷声。刹那间,狂风大作,乌云布满了天空,紧接着豆大的雨点从天空中打落下来,只见前方出现了一个披头散发、青面獠牙的怪物,低沉着声音说:“呵呵,既然你们来到这,只能活下来一个!”。小a和他的小伙伴都惊呆了!

题目描述

瞬间,地面上出现了一个n*m的巨幅矩阵,矩阵的每个格子上有一坨0~k不等量的魔液。怪物各给了小a和uim一个魔瓶,说道,你们可以从矩阵的任一个格子开始,每次向右或向下走一步,从任一个格子结束。开始时小a用魔瓶吸收地面上的魔液,下一步由uim吸收,如此交替下去,并且要求最后一步必须由uim吸收。魔瓶只有k的容量,也就是说,如果装了k+1那么魔瓶会被清空成零,如果装了k+2就只剩下1,依次类推。怪物还说道,最后谁的魔瓶装的魔液多,谁就能活下来。小a和uim感情深厚,情同手足,怎能忍心让小伙伴离自己而去呢?沉默片刻,小a灵机一动,如果他俩的魔瓶中魔液一样多,不就都能活下来了吗?小a和他的小伙伴都笑呆了!

现在他想知道他们都能活下来有多少种方法。

输入输出格式

输入格式:

第一行,三个空格隔开的整数n,m,k

接下来n行,m列,表示矩阵每一个的魔液量。同一行的数字用空格隔开。

输出格式:

一个整数,表示方法数。由于可能很大,输出对1 000 000 007取余后的结果。

输入样例:

2 2 3

1 1

1 1

输出样例:

4

 

说明

【题目来源】

lzn改编

【样例解释】

样例解释:四种方案是:(1,1)->(1,2),(1,1)->(2,1),(1,2)->(2,2),(2,1)->(2,2)。

【数据范围】

对于20%的数据,n,m<=10,k<=2

对于50%的数据,n,m<=100,k<=5

对于100%的数据,n,m<=800,1<=k<=15

 

这道题目一开始我想写暴力,用dfs来找到每一种符合的情况。然而暴力也写了好久,最后明白因为俩人只能往右或者往下走,因此不需要判重,而且条件就是不越界,符合两者之差为0结果就+1,费尽周折骗到了30分。注意dfs时要考虑到该谁走了,这样最后能判断两者差为0是不是uim走的。

(30分暴力代码)

#include<cstdio>
#include<iostream>
#define ll long long
#define mod 1000000007
using namespace std;
const int maxn=810;
int n,m;
int k;
ll ans=0;
int a[maxn][maxn];
void dfs(int x,int y,int w,int turn)
{
	if(turn%2==0) w+=a[x][y];
	else w-=a[x][y];
	w=(w%k+k)%k;
	if(w==0&&turn%2==1) ans++;
	if(y+1<=m) dfs(x,y+1,w,turn+1);
	if(x+1<=n) dfs(x+1,y,w,turn+1);
}
int main()
{
	scanf("%d%d",&n,&m);
	scanf("%d",&k);
	k=k+1;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			scanf("%d",&a[i][j]);
		}
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			dfs(i,j,0,0);
		}
	}
	
	cout<<ans%mod<<endl;
	return 0;
}

标签是动态规划,所以我们可以尝试用动归的方法解决。

我们定义一个dp[i][j][k][turn] 其中i,j代表要到的那个点坐标,k代表出发点到目标点之差为0-k,turn取0或1,代表是小a或是uim走的这一步。读入数据后,首先为每一个a[i][j]赋初始值dp[i][j][a[i][j]%k][0]=1,因为第一步一定是小a走的所以turn为0.接下来就是状态转移方程,我们枚举i从1-n,j从1-m,p从0- k-1(注意,我们在读入k后就立刻给k++,因为当收集到k+1的药水时相当于给自己所拥有的药水量取mod,所以这里枚举0-k-1)。这里分两种情况讨论。如果动归的这一步是小a走的,除了这一步之外得加上上一步的权值,因为小a走这一步会给药水量+a[i][j],所以上一步是p-a[i][j],这有可能是负数,所以我们用(p-a[i][j]+k)%k来保证取mod后是正数。同理可以求出这一步是1的情况,要注意每一次得到新的情况后要取膜,最后还要对ans取一次膜才能得到正确的结果。得到转移方程后,交上去却只得了85分,3个点MLE。研究后,发现是long long类型太大了,会让程序爆掉,于是我把long long全部换成int,这道题也就解决了。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cmath>
#include<cstring>
#define ll long long
using namespace std;
const int maxn=810;
const int maxk=20;
const int mod=1e9+7;
int dp[maxn][maxn][maxk][2]; //i,j,k=wa-wb,who's turn
int a[maxn][maxn];
int ans;
int n,m,k;
int main()
{
	scanf("%d%d%d",&n,&m,&k);
	k++;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			scanf("%d",&a[i][j]);
			dp[i][j][a[i][j]%k][0]=1;
		}
	}
	
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			for(int p=0;p<=k-1;p++)
			{
				int t0=(p-a[i][j]+k)%k;
				int t1=(p+a[i][j])%k;
				dp[i][j][p][0]=(dp[i][j][p][0]+dp[i][j-1][t0][1])%mod;
				dp[i][j][p][0]=(dp[i][j][p][0]+dp[i-1][j][t0][1])%mod;
				dp[i][j][p][1]=(dp[i][j][p][1]+dp[i][j-1][t1][0])%mod;
				dp[i][j][p][1]=(dp[i][j][p][1]+dp[i-1][j][t1][0])%mod;
			}
			
	
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			ans+=dp[i][j][0][1];
			ans%=mod;
		}
	}
	cout<<ans<<endl;
	return 0;
}

总体来说,这个方程也不是那么容易能想到的。多做几道类似的题,以后可能会有不同的启发。

题目列表 User Avatar 主页 题库 网校 训练题单 比赛 评测记录 讨论区 文章广场 更多功能 图片上传 云剪贴板 主题商店 咕值排名 等级分排名 洛谷有题 工单/反馈 相关链接 帮助中心 联系我们 社区规则 陶片放逐 管理名单 tttyy01 个人中心 用户设置 练习情况 我的题库 我的专栏 收藏夹 我的工单 锁定登出 P1164 小A点菜 提交答案加入题单复制题目 提交 230.52k 通过 110.78k 时间限制 1.00s 内存限制 512.00MB 题目编号 P1164 提供者 洛谷 难度 普及− 历史分数 暂无 提交记录 查看题解 题目反馈 标签 洛谷原创 相关讨论进入讨论版 推荐题目 复制 Markdown 中文 进入 IDE 模式 题目背景 uim 神犇拿到了 uoi 的 ra(镭牌)后,立刻拉着基友小 A 到了一家……餐馆,很低端的那种。 uim 指着墙上的价目表(太低级了没有菜单),说:“随便点”。 题目描述 不过 uim 由于买了一些书,口袋里只剩 M 元 (0<M≤10000)。 餐馆虽低端,但是菜品种类不少,有 N 种 (1≤N≤100),第 i 种卖 a i ​ 元 (0<a i ​ ≤1000)。由于是很低端的餐馆,所以每种菜只有一份。 小 A 奉行“不把钱吃光不罢休”的原则,所以他点单一定刚好把 uim 身上所有钱花完。他想知道有多少种点菜方法。 由于小 A 肚子太饿,所以最多只能等待 1 秒。 输入格式 第一行两个整数 N M,分别表示菜品种类 uim 身上的钱数。 第二行 N 个正整数 a i ​ (可能有重复),用空格隔开,分别表示每种菜的价格。 输出格式 一个正整数,表示点菜方案数,保证答案的范围在 [0,2 31 −1] 之内(不超过 C/C++的 int 范围)。 输入输出样例 输入 #1复制 4 4 1 1 2 2 输出 #1复制 3 说明/提示 2020.8.29,增添一组 hack 数据 by @yummy 加入题单 操作 加入做题计划 加入个人题单 加入团队题单 保存 复制题目 目标团队 获取团队中... 保存
最新发布
11-24
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值