寒假训练递推专题

A - 上台阶2

小瓜想走上一个一共有n级的台阶,由于小瓜的腿长比较特殊,他一次只能向上走1级或者3级或者5级台阶。小瓜想知道他有多少种方法走上这n级台阶,你能帮帮他吗?
这道题就是简单的动规,明显可以算出 dp[n]=dp[n-1]+dp[n-3]+dp[n-5].
前五个需要手算下。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+100;
const int mod=100003;
int f[maxn];
int main()
{
	int n;
	scanf("%d",&n);
	f[0]=1;
	f[1]=1; f[2]=1; f[3]=2; f[4]=3; f[5]=5;
	for(int i=6;i<=100003;i++){
		f[i]=(f[i-1]+f[i-3]+f[i-5])%mod;
	}
	printf("%d\n",f[n]);
	return 0;
}

B - 数字三角形

图1给出了一个数字三角形。从三角形的顶部到底部有很多条不同的路径。对于每条路径,把路径上面的数加起来可以得到一个和,你的任务就是找到最大的和。

注意:路径上的每一步只能从一个数走到下一层上和它最近的左边的那个数或者右边的那个数。
动态规划的基本思想:将一个问题分解为子问题递归求解,并且将中间结果保存以避免重复计算的办法,可以称为“动态规划”。动态规划通常用来求最优解.能用动态规划解决的求最优解问题,必须满足最优解的每个局部解也都是最优的.dp[i][j]表示i行j列时的最大值。可得出状态转移方程
dp[i][j]=max(dp[i-1][j-1],dp[i-1][j])+a[i][j];

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+100;
int a[110][110],f[110];
int n,m;
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=i;j++)
			scanf("%d",&a[i][j]);
	for(int i=2;i<=n;i++){
		for(int j=1;j<=i;j++){
			a[i][j]=max(a[i-1][j-1],a[i-1][j])+a[i][j];
		}
	}
	int mx=0;
	for(int i=1;i<=n;i++){
		mx=max(mx,a[n][i]);
	}
	cout<<mx<<endl;
	return 0;
}

C - 矩阵取数问题

一个N*N矩阵中有不同的正整数,经过这个格子,就能获得相应价值的奖励,从左上走到右下,只能向下向右走,求能够获得的最大价值。

例如:3 * 3的方格。

1 3 3

2 1 3

2 2 1

能够获得的最大价值为:11。

这道题跟上道题几乎一样,关键就是划分为子问题,找最优解。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+100;
int a[550][550],f[550];
int main()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			scanf("%d",&a[i][j]);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++){
			a[i][j]=max(a[i-1][j],a[i][j-1])+a[i][j];
		}
	printf("%d\n",a[n][n]);
	return 0;
}

D - 背包问题

在N件物品取出若干件放在容量为W的背包里,每件物品的体积为W1,W2……Wn(Wi为整数),与之相对应的价值为P1,P2……Pn(Pi为整数)。求背包能够容纳的最大价值。

其中1 <= N <= 100,1 <= W <= 10000,每个物品1 <= Wi, Pi <= 10000。

这是经典的01背包问题,具体可以参考这篇博客01背包

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+100;
int f[maxn],w[110],v[110];
int n,m;
int main()
{
	cin>>n>>m;
	f[0]=0;
	for(int i=1;i<=n;i++) scanf("%d %d",&w[i],&v[i]);
	for(int i=1;i<=n;i++){
		for(int j=m;j>=w[i];j--){
			f[j]=max(f[j],f[j-w[i]]+v[i]);
		}
	}
	int ans=0;
	printf("%d\n",f[m]);
	return 0;
}

E - 完全背包

有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的体积是v[i],价值是c[i]。

现在请你选取一些物品装入背包,使这些物品的体积总和不超过背包容量,且价值总和最大。

其中1<=N<=100,1<=V<=50000,1<=v[i],c[i]<=10000。

完全背包也是常见背包问题,算是01背包的变形。具体可以看这篇博客完全背包

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+100;
int f[maxn],w[110],v[110];
int n,m;
int main()
{
	cin>>n>>m;
	f[0]=0;
	for(int i=1;i<=n;i++) scanf("%d %d",&w[i],&v[i]);
	for(int i=1;i<=n;i++){
		for(int j=w[i];j<=m;j++){
			f[j]=max(f[j],f[j-w[i]]+v[i]);
		}
	}
	int ans=0;
	printf("%d\n",f[m]);
	return 0;
}

F - 背包问题 V2

有N种物品,每种物品的数量为C1,C2…Cn。从中任选若干件放在容量为W的背包里,每种物品的体积为W1,W2…Wn(Wi为整数),与之相对应的价值为P1,P2…Pn(Pi为整数)。求背包能够容纳的最大价值。

其中1 <= N <= 100,1 <= W <= 50000,1 <= Wi, Pi <= 10000, 1 <= Ci <= 200。

这道题是多重背包,如果朴素做法三重循环会超时,这就需要二进制优化转化为01背包。
(1)我们知道转化成01背包的基本思路就是:判断每件物品我是取了你好呢还是不取你好。
(2)我们知道任意一个实数可以由二进制数来表示,也就是20~2k其中一项或几项的和。
(3)这里多重背包问的就是每件物品取多少件可以获得最大价值。
可以参考这篇博客多重背包

#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
const int N=1e5+10;
int v[N],w[N],f[N];
int cnt;
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        int k=1;
        while(c>=k)
        {
            cnt++;
            v[cnt]=a*k;
            w[cnt]=b*k;
            c-=k;
            k*=2;
        }
        if(c>0) 
        {
            cnt++;
            v[cnt]=a*c;
            w[cnt]=b*c;
        }
    }
    for(int i=1;i<=cnt;i++)
    {
        for(int j=m;j>=v[i];j--)
        {
            f[j]=max(f[j],f[j-v[i]]+w[i]);
        }
    }
    cout<<f[m]<<endl;
    return 0;
}

G - 最长上升子序列

一个数的序列bi,当b1 < b2 < … < bS的时候,我们称这个序列是上升的。对于给定的一个序列(a1, a2, …, aN),我们可以得到一些上升的子序列(ai1, ai2, …, aiK),这里1 <= i1 < i2 < … < iK <= N。比如,对于序列(1, 7, 3, 5, 9, 4, 8),有它的一些上升子序列,如(1, 7), (3, 4, 8)等等。这些子序列中最长的长度是4,比如子序列(1, 3, 5, 8).
你的任务,就是对于给定的序列,求出最长上升子序列的长度。
这是经典的动态规划题。原理可以看这篇博客各种序列问题里面有很多经典序列问题讲解。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+100;
int a[maxn],f[maxn];
int main()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=n;i++){
		f[i]=1;
		for(int j=1;j<i;j++){
			if(a[i]>a[j]) f[i]=max(f[i],f[j]+1);
		}
	}
	int ans=0;
	for(int i=1;i<=n;i++) ans=max(ans,f[i]);
	cout<<ans<<endl;
	return 0;
}

H - 最长公共子序列Lcs

给出两个字符串A B,求A与B的最长公共子序列(A,B的长度 <= 1000,子序列不要求是连续的)。
比如两个串为:
A:abcicba B:abdkscab
ab是两个串的子序列,abc也是,abca也是,其中abca是这两个字符串最长的子序列。

此题的切入点就是动态规划,通过动归来确定哪些字符是最长公共子序列中的字符,mat[i][j] 表示第一个序列的前i个字符和第二个序列的前j个字符的公共子序列,动态转移方程为:
dp[i][j] = max(dp[i-1][j], dp[i][j-1],dp[i-1][j-1] + (A[i]==B[j] ? 1 : 0)),表示在这三种状态中取到最大值,
(1)第一种状态表示不录入第一个序列的第i个字符时的最长公共子序列,
(2)第二种状态表示不录入第二个序列的第j个字符时的最长公共子序列,
(3)第三种状态表示第一个序列的前i-1个字符与第二个序列前j-1个字符的公共子序列加上最后一个字符的录入状态,如果最后的一个字符相等则录入状态为1,否则为0。
然后根据动归的状态,来判断我们要求得的序列中的字符有哪些。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+100;
int f[1010][1010];
char a[1100],b[1100],c[1100];
int main()
{
	scanf("%s",a);
	scanf("%s",b);
	int l1=strlen(a);
	int l2=strlen(b);
	for(int i=1;i<=l1;i++){
		for(int j=1;j<=l2;j++)
		{
			if(a[i-1]==b[j-1]){
				f[i][j]=f[i-1][j-1]+1; 
			}
			else f[i][j]=max(f[i-1][j],f[i][j-1]);
		}
	}
	int i=l1,j=l2,z=0;
	while(i!=0&&j!=0)
	{
		if(a[i-1]==b[j-1])
		{
			i--;j--;
			c[z++]=a[i];
		}
		else if(f[i-1][j]<f[i][j-1]) j--;
		else if(f[i-1][j]>=f[i][j-1]) i--;
	}
	for(i=z-1;i>=0;i--){
		printf("%c",c[i]); 
	}
	printf("\n");
	return 0;
}

I - 石子合并

将 nn 堆石子绕圆形操场排放,现要将石子有序地合并成一堆。规定每次只能选相邻的两堆合并成新的一堆,并将新的一堆的石子数记做该次合并的得分。
请编写一个程序,读入堆数 nn 及每堆的石子数,并进行如下计算:
1.选择一种合并石子的方案,使得做 n-1n−1 次合并得分总和最大。
2.选择一种合并石子的方案,使得做 n-1n−1 次合并得分总和最小。

石子合并也是经典的区间dp,这题需要注意的是环形排放。关于石子合并的原理可看y总的的视频讲解石子合并

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+100;
const int inf=0x3f3f3f3f;
int f[330][330],a[330],sum[510];
int n;
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++) scanf("%d",&a[i]),a[i+n]=a[i];
	for(int i=1;i<=n*2;i++) sum[i]=sum[i-1]+a[i];
//	for(int i=1;i<=n*2;i++) printf("%d ",sum[i]);
//	printf("\n"); 	
	for(int len=2;len<=n;len++){
		for(int l=1;l<=n*2-len+1;l++){//左端点 
			int r=l+len-1;
			f[l][r]=inf;
			for(int k=l;k<r;k++)
				f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]+sum[r]-sum[l-1]);
		}
	}
	int mn=1e9;
	for(int i=1;i<=n;i++){
		//cout<<f[i][i+n-1]<<endl;
		mn=min(mn,f[i][i+n-1]);
	}
	for(int len=2;len<=n;len++){
		for(int l=1;l<=n*2-len+1;l++){//左端点 
			int r=l+len-1;
			f[l][r]=0;
			for(int k=l;k<r;k++){
				f[l][r]=max(f[l][r],f[l][k]+f[k+1][r]+sum[r]-sum[l-1]);
			}
		}
	}
	int mx=0;
	for(int i=1;i<=n;i++) mx=max(mx,f[i][i+n-1]);
	printf("%d\n",mn); 
	printf("%d\n",mx);
	return 0;
}

J - 循环数组最大子段和

N个整数组成的循环序列a[1],a[2],a[3],…,a[n],求该序列如a[i]+a[i+1]+…+a[j]的连续的子段和的最大值(循环序列是指n个数围成一个圈,因此需要考虑a[n-1],a[n],a[1],a[2]这样的序列)。当所给的整数均为负数时和为0。
例如:-2,11,-4,13,-5,-2,和最大的子段为:11,-4,13。和为20。
这道题与最大子段和不同的是可以首尾连,想下最优解要么在正常的序列中,要么就在首尾相连的部分中。所以取 最大子段和与所有数和减去最小子段和这两个中取最大值。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+100;
int a[maxn],n;
ll sum=0;
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
    	scanf("%d",&a[i]);
    	sum+=a[i];
	}  	
	ll mx=0,s=0;
    for(int i=1;i<=n;i++)//找最大子段
    {
    	s+=a[i];
    	if(s<0) s=0; 
		mx=max(s,mx);
    }
	ll mn=0x3f3f3f3f; s=0;
    for(int i=1;i<=n;i++)
    {
    	s+=a[i];
    	if(s>0) s=0;
    	mn=min(mn,s);
    }
    mx=max(mx,sum-mn);
    printf("%lld\n",mx);
    return 0;
}

K - 没有上司的舞会

某大学有N个职员,编号为1~N,校长的编号为1,他们之间有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子结点的直接上司。
现在有个周年庆宴会,宴会每邀请来一个职员都会增加一定的快乐指数Ri,但是呢,如果某个职员的直接上司来参加舞会了,那么这个职员就无论如何也不肯来参加舞会了。所以,请你编程计算,邀请哪些职员可以使快乐指数最大,求最大的快乐指数。
这题是经典的树形dp题,建个二维数组f[i][0]表示不取这个结点 ,
f[i][1]表示取这个结点,还要构件图,dfs跑一下每次取最优解。
这个·需要注意的是双向边,构建图时注意范围。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2e5+100;
int a[maxn],n;
int f[maxn][3];
int e[maxn],ne[maxn],h[maxn],id;
bool st[maxn];
void add(int a,int b)
{
	e[id]=b; ne[id]=h[a]; h[a]=id++;
}
void dfs(int u,int d)
{
	f[u][1]=a[u];
	for(int i=h[u];i!=-1;i=ne[i])
	{
		int j=e[i];
		if(j==d) continue;
		dfs(j,u);
		f[u][0]+=max(f[j][0],f[j][1]);
		f[u][1]+=f[j][0];
	}
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	memset(h,-1,sizeof(h));
	for(int i=1;i<n;i++)
	{
		int a,b;
		scanf("%d %d",&a,&b);
		add(b,a);
		add(a,b);
	}
	int root=1;
	dfs(1,0);
	printf("%d\n",max(f[root][1],f[root][0]));
	return 0; 
}

L - 滑雪

Michael喜欢滑雪百这并不奇怪, 因为滑雪的确很刺激。可是为了获得速度,滑的区域必须向下倾斜,而且当你滑到坡底,你不得不再次走上坡或者等待升降机来载你。Michael想知道载一个区域中最长的滑坡。区域由一个二维数组给出。数组的每个数字代表点的高度。下面是一个例子
1 2 3 4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9
一个人可以从某个点滑向上下左右相邻四个点之一,当且仅当高度减小。在上面的例子中,一条可滑行的滑坡为24-17-16-1。当然25-24-23-…-3-2-1更长。事实上,这是最长的一条。
这题是经典的记忆化搜索题 ,就是搜索加上动规的思想,每次搜索的子问题都取最优解 可参考记忆化搜索

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+100;
int d[4][2]={{1,0},{-1,0},{0,1},{0,-1}};
int vis[110][110],a[110][110];
int n,m;
int dfs(int x,int y)
{
	int k=1;
	if(vis[x][y]) return vis[x][y];
	for(int i=0;i<4;i++)
	{
		int xx=x+d[i][0];
		int yy=y+d[i][1];
		if(xx>=1&&xx<=n&&yy>=1&&yy<=m&&a[xx][yy]>a[x][y])
		{
			int u=dfs(xx,yy)+1;
			if(u>k) k=u; 
		}
	}
	vis[x][y]=k;
	return k;
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			scanf("%d",&a[i][j]);
	int mx=0,k;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			k=dfs(i,j);
			mx=max(k,mx);
		} 
	}
	printf("%d\n",mx);
	return 0;
} 
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值