ACM-校赛预热

动态规划

找对状态转移方程

找到状态转移方程。找状态转移方程需要找清状态转移的节点,用什么来区分两个状态。
eg: 数字金字塔,区分两个状态的节点是向左还是向右。(即向左是一个状态,向右是一个状态)
eg:背包问题,节点可以个人理解为是否往背包中放。(即放进去是一个状态,不放进去又是一个状态)
找好节点之后,就是择优选取,按照题目要求通过对两个状态进行择优状态转移。

数字三角形

(经典经典经典)
输出最大的和可以算对,但是记录路径自己的方法却一直wa(不知道一月份集训的时候是怎么A的,反正过了三个月记录路径就是出错。)

思路:
dp[i-1][j] = max(dp[i][j] , dp[i][j+1]) + a[i-1][j];
对该状态转移方程的分析:求和是从下往上求,遍历也应从下往上遍历,dp数组来保存这个点到最底层最优解,然后逐层向上取优。
(注意点,当时自己犯了一个低级错误,就是在遍历是对边界的处理,在这一层会有j+1这个遍历操作,因此j应该小于等于i-1,不然会越界。c++数组越界这一点没有Java容易发现,Java一越界直接控制台一片红。)

代码(WA代码):

#include<bits/stdc++.h>
using namespace std;
#define maxn 105
int a[maxn][maxn];
int dp[maxn][maxn];
int keep[maxn][maxn];
int main(int argc, char const *argv[])
{
	int n;
	freopen("c.txt","r",stdin);
	cin>>n;
	for (int i = 0; i < n; ++i)
	{
		for (int j = 0; j <= i; ++j)
		{
			cin>>a[i][j];
			dp[i][j]=a[i][j];
		}
	}
	for (int i = n-1; i >=1; i--)
	{
		for (int j = 0; j <=i-1 ; ++j)   //此时筛选的时候要考虑到每次都会遍历到靠右的元素,所以i-1
		{
			if (dp[i][j]>dp[i][j+1])
			{
				dp[i-1][j] = dp[i][j]+a[i-1][j];
				keep[i-1][j] = a[i][j];
			}
			else {
				dp[i-1][j] = dp[i][j+1]+a[i-1][j];
				keep[i-1][j] = a[i][j+1];
			}
			// dp[i-1][j] = max(dp[i][j],dp[i][j+1]) + a[i-1][j];      //从底下网上逐层筛选
		}
	}
	cout<<dp[0][0]<<endl;
	// 还在wa的记录路径
	int markx,marky = 0;
	while(markx<n){
		cout<<a[markx][marky]<<" ";
		if (keep[markx][marky]==a[markx+1][marky])
		{
			markx = markx + 1 ;
			marky = marky;
		}
		else {
			markx = markx + 1 ;
			marky = marky + 1 ;
		}
	}
	return 0;
}

01背包-hdu2602

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2602

题干大意:典型的01背包问题:有若干个骨头,每个骨头有体积、有价值,在一定体积内要最大的价值和,问这个最大的价值和为多少?

准备的数组:
存放每个骨头价值的value数组
存放每个骨头体积的v数组
另开一个dp数组,dp[i][j]来记录在放第i个骨头的时候容器的容积为j时得到的最大价值。(因此,最后我们只需输出dp[骨头数][容器的容积])

伪代码

for(从第(i++)个骨头开始遍历)
	for(体积 j 从0开始++if( j >= 第i个骨头的体积 ) dp[i][j] = max(dp[i-1][j],dp[i-1][j-v[i]]+value[i])   
		else dp[i][j] = dp[i-1][j]
输出dp[骨头数][容器的容积]

状态转移方程的理解

dp[i][j]的值取决于放还是不放这两个状态,(在这之前还要判断这个放的条件必须满足,即j大于等于这个体积)如果不放更符合题意,在决定是否放第i个骨头的时候不放,让剩下的体积去放除了这块的其他骨头比放这块更值; 如果放更符合题意,则在决定放第i个骨头的时候放,让剩下的体积减去决定放下的这个骨头的体积,然后得到了一个这个骨头的价值加上减去这个骨头体积时候的价值。

#include<bits/stdc++.h>
using namespace std;
#define maxn 1050
int value[maxn];
int v[maxn];
int dp[maxn][maxn];

int main(int argc, char const *argv[])
{
	int T;
	cin>>T;
	while(T--){
		memset(dp,0,sizeof(dp));
		int num , vtotal;
		cin>>num>>vtotal;
		for (int i = 1; i <= num; ++i)
		{
			cin>>value[i];
		}
		for (int i = 1; i <= num; ++i)
		{
			cin>>v[i];
		}
		for (int i = 1; i <= num; ++i)
		{
			for (int j = 0; j <= vtotal; j++)  //每次当遍历到下一个对象时判断是否进行放入
			{
				if (j>=v[i])
				{
					dp[i][j] = max(dp[i-1][j],dp[i-1][j-v[i]]+value[i]);  //此时的dp[i-1][j-v[i]] 是前面已经求出来的
				}
				else {
					dp[i][j] = dp[i-1][j];
				}
			}
		}
		cout<<dp[num][vtotal]<<endl;
	}
	return 0;
}

图表的动态规划-洛谷P1002

题目链接:https://www.luogu.org/problemnew/show/P1002

题干大意:一个棋盘,从其上选取一个点放置一个马,从左下角开始出发的一个卒,不能走到马所在的点以及马的日字所能覆盖的点,一共八个点。 问这个卒可以走到右上角的总数?

做题的历程:最一开始做的时候只是想一个dfs判断到右上角ans++,后来发现tle(现在想了想也不知道当时怎么想的,dfs应该是做不出来的啊),想了一下,需要开一个dp数组来储存从这个点到右上角的次数。然后通过递归来一步一步计算最左下角的那个点的次数。及最后输出dp[][]左下角的值。

易错点,wa了两次,因为没看到数可能很大,然后爆掉了int , 墨迹半天改成longlong A了。

代码(AC的):

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define maxn 25
#define ll long long

int is_ok[maxn][maxn];
// int is_vis[maxn][maxn];  //最一开始用dfs时开的没用数组
ll int dp[maxn][maxn];
int m,n;
ll int ans;
int hx,hy;

bool is_in(int x,int y){
	if (x>=0&&x<=n&&y>=0&&y<=m)
	{
		return true;
	}
	return false;
}

void init(int x,int y){
	is_ok[x][y]=1;
	if (is_in(x-2,y-1))
	{ 
		is_ok[x-2][y-1]=1;
	}
	if (is_in(x-1,y-2))
	{
		is_ok[x-1][y-2]=1;
	}
	if (is_in(x+1,y-2))
	{
		is_ok[x+1][y-2]=1;
	}
	if (is_in(x+2,y-1))
	{
		is_ok[x+2][y-1]=1;
	}
	if (is_in(x-2,y+1))
	{
		is_ok[x-2][y+1]=1;
	}
	if (is_in(x-1,y+2))
	{
		is_ok[x-1][y+2]=1;
	}
	if (is_in(x+1,y+2))
	{
		is_ok[x+1][y+2]=1;
	}
	if (is_in(x+2,y+1))
	{
		is_ok[x+2][y+1]=1;
	}
}

ll int compute(int x,int y){
	for (int i = 0; i < 2; ++i)
	{
		if (x==n&&y==m)
		{
			return 1;
		}
		else if (is_ok[x][y]==0)
		{
			if (is_in(x,y+1)&&is_in(x+1,y))
			{
				if (dp[x][y]==0)
				{
					dp[x][y]=compute(x,y+1)+compute(x+1,y);
				}
				else return dp[x][y]; 
			}
			else if (is_in(x+1,y)&&!is_in(x,y+1))
			{
				if (dp[x][y]==0)
				{
					dp[x][y]=compute(x+1,y);
				}
				else return dp[x][y]; 
			}
			else if (!is_in(x+1,y)&&is_in(x,y+1))
			{
				if (dp[x][y]==0)
				{
					dp[x][y]=compute(x,y+1);
				}
				else return dp[x][y]; 
			}
			else return 0;
		}
	}
	return 0;
}

int main(int argc, char const *argv[])
{
	// freopen("f.txt","r",stdin);
	memset(is_ok,0,sizeof(is_ok));
	memset(dp,0,sizeof(dp));
	scanf("%d%d%d%d",&n,&m,&hx,&hy);
	init(hx,hy);
	cout<<compute(0,0)<<endl;
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值