CSP动态规划专训题解

比赛请见这里

虽然是赛时发的,但仍然是赛后题解

P1216 数字三角形 Number Triangles

这道题是一道最基础不过的迪屁了。如果你使用超级无敌大爆搜的话,那你这道题肯定会 T T T 掉。我们需要把每个数加上它左下角和右下角中最大的数,从倒数第二行开始,一层一层往前递推,答案就出来了。

状态转移方程:

d p [ i ] [ j ] + = m a x { d p [ i + 1 ] [ j ] , d p [ i + 1 ] [ j + 1 ] } dp[i][j]+=max\{dp[i+1][j],dp[i+1][j+1]\} dp[i][j]+=max{dp[i+1][j],dp[i+1][j+1]}

#include<bits/stdc++.h>

using namespace std;

int a[1005][1005]; 



int main(int argc,char *argv[])

{

	int n;

	cin >> n;

	for(int i=1;i<=n;++i)

	{

		for(int j=1;j<=i;++j)

		{

			cin >> a[i][j];

		}

	}

	for(int i=n-1;i>=1;--i)

	{

		for(int j=1;j<=i;++j)

		{

			a[i][j] += max(a[i+1][j],a[i+1][j+1]);

		}

	} 

	cout << a[1][1];

	return 0;

}

懂的都懂,不懂建议去百度一下

P1507 NASA的食物计划

这是一道非常经典的01背包型动态规划,就是每个东西只能选一次,求最大值,请点这里。我们首先需要开一个 d p dp dp 二维数组,下标为体积和质量。这时候有些人就要问了:你的博客里面的动态规划那题不是一维数组吗?这里怎么变成二维的了?

这是因为那里只有一个限制条件,但这里有两个限制条件,所以要开二维,当循环完后, d p [ n ] [ m ] dp[n][m] dp[n][m] 就是最优解。

状态转移方程:

d p [ j ] [ k ] = m a x { d p [ j ] [ k ] , d p [ j − a 1 [ i ] ] [ j − a 2 [ i ] ] + v [ i ] } dp[j][k]=max\{dp[j][k],dp[j-a1[i]][j-a2[i]]+v[i]\} dp[j][k]=max{dp[j][k],dp[ja1[i]][ja2[i]]+v[i]}

其中第一层循环为 i i i ,第二层循环为 j j j ,第三层循环为 k k k a 1 a1 a1 为体积, a 2 a2 a2 为质量, v v v 为卡路里。

#include<bits/stdc++.h>

using namespace std;

int dp[405][405],s1,s2,n,a1,a2,v;



int main(int argc,char *argv[])

{

	cin >> s1 >> s2 >> n;

	for(int i=1;i<=n;++i)

	{

		cin >> a1 >> a2 >> v;

		for(int j=s1;j>=a1;--j)

		{

			for(int k=s2;k>=a2;--k)

			{

				dp[j][k] = max(dp[j][k],dp[j-a1][k-a2]+v);

			}

		}

	}

	cout << dp[s1][s2];

	return 0;

}

P1060 开心的金明

老经典题了,博客里有讲,这里直接贴动态转移方程:

d p [ j ] = m a x { d p [ j ] , d p [ j − v [ i ] ] + x [ i ] dp[j]=max\{dp[j],dp[j-v[i]]+x[i] dp[j]=max{dp[j],dp[jv[i]]+x[i]

#include<bits/stdc++.h>

using namespace std;

int n,m,w[35],v[35],x[35],f[50000];



int main(int argc,char *argv[])

{

	cin >> n >> m;

	for(int i=1;i<=m;++i)

	{

		cin >> v[i] >> w[i];

		x[i] = v[i]*w[i];

	}

	for(int i=1;i<=m;++i)

	{

		for(int j=n;j>=v[i];--j)

		{

			f[j]=max(f[j],f[j-v[i]]+x[i]);		

		}

	}

	cout << f[n];

	return 0;

}

当然你用 d f s dfs dfs 切掉也可以

P1359 租用游艇

这一题,是求最小值,是一道完全背包,我们在做的时候一定要小心他的顺序,因为他是一个倒三角形。其他的只要想明白,就很容易推出状态转移方程:

d p [ i ] = m i n { d p [ i ] , d p [ j ] + a [ i ] [ j ] } dp[i]=min\{dp[i],dp[j]+a[i][j]\} dp[i]=min{dp[i],dp[j]+a[i][j]}

#include<bits/stdc++.h>

using namespace std;

int n,a[205][205],dp[205];



int main(int argc,char *argv[])

{

	cin >> n;

	for(int i=1;i<n;++i)

	{

		for(int j=i+1;j<=n;++j)

		{

			cin >> a[i][j];

		} 

		dp[i] = 1e9;

	}

	for(int i=n-1;i>=1;--i)

	{

		for(int j=i+1;j<=n;++j)

		{

			dp[i] = min(dp[i],a[i][j]+dp[j]);

		}

	}

	cout << dp[1];

	return 0;

}

P1048 采药

啊这题也太水了吧???

跟开心的金明一毛一样,只不过限制条件换成了时间。。。
#include<bits/stdc++.h>

using namespace std;

int t,m,a[105],b[105],dp[1005];



int main(int argc,char *argv[])

{

	cin >> t >> m;

	for(int i=1;i<=m;++i)

	{

		cin >> a[i] >> b[i];

	}

	for(int i=1;i<=m;++i)

	{

		for(int j=t;j>=0;--j)

		{

			if(j>=a[i])

				dp[j] = max(dp[j],dp[j-a[i]]+b[i]);

		}

	}

	cout << dp[t];

	return 0;

} 

简单吧?

好,接下来是唯一不用动态规划的题:

P1095 守望者的逃离

刚看这题你一定特别懵。其实很简单,就是一个人跑步,但他有召唤师技能闪现。他的闪现不用 C D CD CD ,但需要蓝。由于他(它?)回蓝的速度会场快,所以闪现终究是比跑步快的。但比如它离终点只有一米,但他还在等闪现,所以答案就会出错,我们只需要把跑步与闪现分为两个变量,如果闪现比跑步快,那就立马把跑步换成闪现,最后稍加判断即可。。。

#include<bits/stdc++.h>

using namespace std;

int m,s,t;



int main(int argc,char *argv[])

{

	cin >> m >> s >> t;

	int run=0,shanxian=0;

	for(int i=1;i<=t;++i)

	{

		run += 17;

		if(m>=10)

			shanxian += 60,m -= 10;

		else 

			m += 4;

		if(shanxian>run)

			run = shanxian;

		if(run>=s)

		{

			cout << "Yes" << endl << i;

			exit(0); 

		}

	}

	cout << "No" << endl << run;

	return 0;

}

想用动态规划的小朋朋其实也不是不可以 ……—)

P1077 摆花

这一题比较简单,不讲了。若有人想听,私信发我。

#include<bits/stdc++.h>

using namespace std;

int n,m,a[105],dp[105]={1};



int main(int argc,char *argv[])

{

	cin >> n >> m;

	for(int i=1;i<=n;++i)

	{

		cin >> a[i];

	}

	for(int i=1;i<=n;++i)

	{

		for(int j=m;j>=0;--j)

		{

			for(int k=1;k<=min(j,a[i]);++k)

			{

				dp[j] = (dp[j]+dp[j-k])%1000007;

			}

		}

	}

	cout << dp[m];

	return 0;

}

P1057 传球游戏

这一题其实想通了就挺简单的。状态转移方程:

d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] + d p [ i − 1 ] [ j + 1 ] dp[i][j]=dp[i-1][j-1]+dp[i-1][j+1] dp[i][j]=dp[i1][j1]+dp[i1][j+1]

我们 d p dp dp 数组里存的是每轮中每个人有多少种方案会传到他,而左边的人和右边的人可以一步传到我们这里,所以这个人被传到的方案数就等于左边的人的方案数加上右边的人的方案数,最后判断边界即可。。。

#include<bits/stdc++.h>

using namespace std;



int main(int argc,char *argv[])

{

	int n,m,dp[35][35];

	memset(dp,0,sizeof(dp));

	dp[0][1] = 1;

	cin >> n >> m;

	for(int i=1;i<=m;++i)

	{

		for(int j=1;j<=n;++j)

		{

			if(j==1) 

			{

				dp[i][j] = dp[i-1][n]+dp[i-1][j+1];

			}

			else if(j==n)

			{

				dp[i][j] = dp[i-1][j-1]+dp[i-1][1]; 

			}

			else

			{

				dp[i][j] = dp[i-1][j-1]+dp[i-1][j+1];

			}

		}

	}

	cout << dp[m][1]; 

	return 0;

}

P2426 删数

数学题不做讲解
#include<bits/stdc++.h>

using namespace std;

int n,a[105],dp[105];



inline int value(int x,int y)

{

	return (x==y ? a[x]:abs(a[x]-a[y])*(y+1-x));

}



int main(int argc,char *argv[])

{

	cin >> n;

	for(int i=1;i<=n;++i)

	{

		cin >> a[i];

		dp[i] = value(1,i);

		for(int j=1;j<=i-1;++j)

		{

			dp[i] = max(dp[j]+value(j+1,i),dp[i]);

		}

	}

	cout << dp[n];

	return 0;

}

最后一题,也就是本比赛中最难的一题来了!!!

P1064 金明的预算方案

首先它相较于开心的金明多了一个概念 —— 附件,所以我们要分开存储。由于每个主件最多有 2 2 2 个附件,所以要开二维数组。这道题在写动态转移方程的时候一共要写四次——

1.只买主件

2.只买主件和第一个附件

3.只买主件和第二个附件

4.全买

所以套上 01 模板,开 g i a o giao giao !!!(我把有几个附件存在了 f w [ i ] [ 0 ] fw[i][0] fw[i][0]

#include<bits/stdc++.h>  //万能头
using namespace std;  //std命名空间
int n,m,dp[35005],w[35005],x[35005],fx[35005][3],fw[35005][3];
//n为钱数,m为要买的东西数量,dp为用来迪屁的数组,w为每个物品的钱数,x为每个物品的乘积,fx为每个附件的乘积,fw为每个附件的钱数

int main(int argc,char *argv[]) //开始了开始了!第一道绿题呢!!!
{
	cin >> n >> m;  //输入总钱数和东西数量
	for(int i=1;i<=m;++i)  //循环处理每个东西
	{
		int v,p,q; //定义变量
		cin >> v >> p >> q; //输入变量
		if(!q) //如果本身是主件
		{
			w[i] = v; //赋值
			x[i] = v*p; //再赋
		}
		else  //如果是附件
		{
			fw[q][0] ++;  //计数器加一
			fw[q][fw[q][0]] = v;  //赋值
			fx[q][fw[q][0]] = v*p;  //赋值
		}
	}
	for(int i=1;i<=m;++i)  //迪屁正式开始:枚举每件物品
	{
		for(int j=n;j>=w[i];--j)  //从高到低循环(01背包)
		{
			dp[j] = max(dp[j],dp[j-w[i]]+x[i]);  //只选主件
		 	
			if(j>=w[i]+fw[i][1])dp[j] = max(dp[j],dp[j-w[i]-fw[i][1]]+x[i]+fx[i][1]);  //只选主件和第一个附件
				
			if(j>=w[i]+fw[i][2])dp[j] = max(dp[j],dp[j-w[i]-fw[i][2]]+x[i]+fx[i][2]);  //只选主件和第二个附件
			
			if(j>=w[i]+fw[i][1]+fw[i][2])
				dp[j] = max(dp[j],dp[j-w[i]-fw[i][1]-fw[i][2]]+x[i]+fx[i][1]+fx[i][2]);  //全选
		}
	}
	cout << dp[n];  //此时第n为为最大值
	return 0;  //程序:哦♂ 我没了
}

谢谢大佬观看,若还是不懂请私信与我,若有大佬看出了错误,请不要喷本渴鹅

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值