luogu1006:传纸条:棋盘DP

97 篇文章 1 订阅
93 篇文章 0 订阅

题目连接

  • 该题是luogu试炼场的2-17:T2

题目大意

  1. n*m的棋盘,每个格子有一个0-100的数值;
  2. 从左上角出发,只能向右和向下走,到达右下角;
  3. 从右下角出发,只能向左和向上走,到达左上角;
  4. 要求2次的路线不能重复,求经过格子的取值和尽可能大。

题目分析

  • 体面非常直观,第一感觉用深搜就可以做,而且只有50的数据,感觉随便搞一搞还能暴力AC;
  • 本题是在DP模块,所以还是用DP的思路来分析:
  1. 其实可以看作,从左上角去右下角2次,走了路线不同,这样就只有向右和向下两个方向,所以状态的推导就已经少了很多;
  2. 要走两次,这就是个纠结的问题,因为路线不能重复,可以理解为每次走两格:(x1,y1),(x2,y2);


思路1:四维DP
  • DP的解题套路,问什么设什么:
  1. f[x1][y1][x2][y2] 表示同时落脚到(x1,y1)和(x2,y2) 的最优解;
  2. 转移方程就需要考虑以下四种情况:
    1)都从横的来;
    2)都从竖的来;
    3)1从横着来,2从竖着来;
    4)1从竖着来,2从横着来;
  3. 所以:
    f[x1][y1][x2][y2]=max(f[x1][y1-1][x2][y2-1],f[x1-1][y1][x2-1][y2],f[x1][y1-1][x2-1][y2],f[x1-1][y1][x2][y2-1])+a[x1][y1]+a[x2][y2];
  4. 因为(x1,y1)和(x2,y2) 不可以重叠,所以枚举的时候做一个判断。
  5. 最后答案应该是:f[n][m-1][n-1][m]。
思路1参考代码
//luogu1006:传纸条:四维DP 
 
#include<bits/stdc++.h>
using namespace std;

int f[60][60][60][60];
int a[60][60];
int n,m;
int maxx(int x,int y,int j,int k)//求4个数字的最大值 
{
	if(x<y) x=y;
	if(x<j) x=j;
	if(x<k) x=k;
	return x;
}
int main()
{
	memset(f,0,sizeof(f));
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++) 
	{
		for(int j=1;j<=m;j++)
		{
			scanf("%d",&a[i][j]);
		}
	}

	for(int x1=1;x1<=n;x1++)
	{
		for(int y1=1;y1<=m;y1++)
		{
			for(int x2=1;x2<=n;x2++)
			{
				for(int y2=1;y2<=m;y2++)
				{
					if(x1==x2&&y1==y2) continue;
					f[x1][y1][x2][y2]=maxx(f[x1][y1-1][x2][y2-1],
										   f[x1-1][y1][x2-1][y2],
										   f[x1][y1-1][x2-1][y2],
										   f[x1-1][y1][x2][y2-1])+a[x1][y1]+a[x2][y2];
				}
			}
		}
	}
	printf("%d",f[n][m-1][n-1][m]);
	
	return 0;
}

思路2:优化成三维DP
  • 基本思路和四维DP的没有变化,但是可以做一个简单的观察分析: 因为(x1,y1)和(x2,y2) 一定是从左上角出发,并且同步进行,所以一定是落在同一个右斜上
  • 所以有:(x1+y1)等于(x2+y2)
  • 因此,本来四层的循环,可以变为三层:i表示当前是第i斜,再分别枚举x1和x2,就可以直接算出对应的坐标了。
  • 这样可以适用于数据增大到1000的测试点。
思路2参考代码:
//luogu1006:传纸条:三维DP 
#include<bits/stdc++.h>
using namespace std;

int f[200][60][60];
int a[60][60];
int n,m;
int maxx(int x,int y,int j,int k)//求4个数字的最大值 
{
	if(x<y) x=y;
	if(x<j) x=j;
	if(x<k) x=k;
	return x;
}
int main()
{
	memset(f,0,sizeof(f));
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++) 
	{
		for(int j=1;j<=m;j++)
		{
			scanf("%d",&a[i][j]);
		}
	}

	for(int i=2;i<=n+m;i++)//枚举右斜 
	{
		for(int x1=1;x1<=n;x1++)
		{
			if(i-x1<1) continue; //跳过y1坐标是负数的情况 
			for(int x2=1;x2<=n;x2++)
			{
				if(i-x2<1) continue; //跳过y2坐标是负数的情况 
	
				f[i][x1][x2]=maxx(f[i-1][x1][x2-1],
								  f[i-1][x1-1][x2],
								  f[i-1][x1][x2],
								  f[i-1][x1-1][x2-1])+a[x1][i-x1]+a[x2][i-x2];
				
				if(x1==x2) //重复格子 
				{
					f[i][x1][x2]-=a[x1][i-x1];
				}	
			}
		}
	}
	printf("%d",f[n+m][n][n]);
	
	return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值