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;
}