【算法每日一练】动态规划 篇18 猫粮规划 接苹果 魔族密码 最大正方形 最大正方形2 奶牛比赛

目录

猫粮规划

思路: 

接苹果

思路:

魔族密码 

思路: 

最大正方形

思路: 

最大正方形 2

思路: 

奶牛比赛 

​编辑 思路:


        

        

猫粮规划

思路: 

 每种食物都有两种状态,记忆化dfs当然可以,但是你是否觉得这个题很想之前讲过的“小A点菜 ”?(【算法每日一练]-动态规划 (保姆级教程 篇2)#尼克的任务 #数楼梯 #小A点菜-CSDN博客)那道题问的是对于那些菜要花光她的钱,一共有多少方案?这道题问的是一个区间罢了,那么既然题目意思都这么像,跑不了解法也基本一致:

还是设置f[i][j]表示遍历i个东西获得j能量对应的方案数,

f[i][j]=f[i-1][j]+f[i-1][j-a[i]]

那么最终直接把符合题意的j全部加起来就行了。代码如下:(注意好初始化就行)

#include <bits/stdc++.h>
using namespace std;
int ans,n,l,r,a[50],f[50][500];
int main(){
	cin>>n>>l>>r;
	for(int i=1;i<=n;i++)cin>>a[i],f[i][0]=1;
	f[0][0]=1;
	for(int i=1;i<=n;i++)
	for(int j=0;j<=r;j++){
		f[i][j]=f[i-1][j];
		if(j-a[i]>=0)f[i][j]+=f[i-1][j-a[i]];
	}
	for(int j=l;j<=r;j++){
		ans+=f[n][j];
	}
	cout<<ans;
}

既然看到转移仅用到了i-1行的数据,那么i放到外面就可以又降一维,变成这样:

    for(int i=1;i<=n;i++)
	for(int j=r;j>=0;j--){
		if(j-a[i]>=0)f[j]+=f[j-a[i]];
	}

就变成了如下的一维解法:

#include <bits/stdc++.h>
using namespace std;
int ans,n,l,r,a[50],f[500];
int main(){
	cin>>n>>l>>r;
	for(int i=1;i<=n;i++)cin>>a[i];f[0]=1;
	for(int i=1;i<=n;i++)
	for(int j=r;j>=a[i];j--){
		f[j]+=f[j-a[i]];
	}
	for(int j=l;j<=r;j++){
		ans+=f[j];
	}
	cout<<ans;
}

        

        

接苹果

思路:

 还是“小A点菜”的变形题,我们不妨设置1表示站在左边树下,2表示在右边数下。然后设置t表示遍历到了多长时间,j表示已经移动多少次。

然后就出来了:f[i][j][1]表示在第i秒在左边树下当前一件移动j次最多接多少个苹果,f[i][j][2]表示在第i秒在右边树下已经移动j次最多接多少个苹果。然后找关系:

 如果当前左树下掉苹果:有

f[i][j][1]=max(f[i-1][j][1],f[i-1][j-1][2])+1;

f[i][j][2]=max(f[i-1][j][2],f[i-1][j-1][1]);

当前在右树下同理。

有两个坑注意一下:

一个是最开始在左边树下,那么j为偶数一定还是在左边,奇数一定是右边

另一个是移动次数最多的不一定是答案!

#include <bits/stdc++.h>
using namespace std;
int ans,t,w,f[1010][50][3],a[1010];
int main(){
	cin>>t>>w;
	for(int i=1;i<=t;i++)cin>>a[i];
	for(int i=1;i<=t;i++)
	for(int j=0;j<=w;j++){
		if(a[i]==1){
			if(j%2==0) f[i][j][1]=max(f[i-1][j][1],f[i-1][j-1][2])+1;
			else f[i][j][2]=max(f[i-1][j][2],f[i-1][j-1][1]);
			
		}
		if(a[i]==2){
			if(j%2==1) f[i][j][2]=max(f[i-1][j][2],f[i-1][j-1][1])+1;
			else f[i][j][1]=max(f[i-1][j][1],f[i-1][j-1][2]);
			
		}
	
	}
	for(int i=1;i<=w;i++)
	ans=max(ans,max(f[t][i][1],f[t][i][2]));
	cout<<ans;//不一定移动的次数越多接到的就越多
}

        

        

魔族密码 

思路: 

注意到词链就是下面的词一定要完全包含前面的,那么不妨把前面的单词看成一个字符,后面多出来的单词也看成一个字符,那就转化成了最长上升子序列了吧。

不过是在转移条件变成了前面的单词时候是后面的前缀。

设置f[i]表示第i个单词结果的词链所包含的最长单词数

f[i]=max(f[j]+1)   j<i      

和最长上升子序列一样,我们的答案不一定是以最后一个字符结尾的,那么就需要都遍历一遍。

补充一下STL:substr(begin,length) 

#include <bits/stdc++.h>
using namespace std;
int n,f[2010],ans;
string s[2010];
int main(){
	cin>>n;
	for(int i=1;i<=n;i++)cin>>s[i];
	for(int i=1;i<=n;i++){
		f[i]=1;
		for(int j=1;j<i;j++){
			if(s[j]==s[i].substr(0,s[j].size()))
				f[i]=max(f[j]+1,f[i]);	
		}
		ans=max(f[i],ans);
	}
	cout<<ans;
}

        

        

最大正方形

思路: 

这个转移方程还是挺难发现的。

一开始设置f[i][j]表示以<i,j>为右下顶点的不含0的最大正方形边长,

那么容易发现f[i][j]=min(f[i-1][j],f[i][j-1])+1,然后发现f[i-1][j]一旦为1,就不对了,主要是没有考虑f[i-1][j-1],那么正确的转移式子应该是:

f[i][j]=min(min(f[i-1][j],f[i][j-1]),f[i-1][j-1])+1

注意:
1,不要见到min就把值初始化成iNF,一定要结合f的具体含义
2,其实应该设置每个格子的f初始化值都是1,不过很麻烦,而且我们只需要在转移的时候把这个1加上即可,这样子是等价的。

3,因为每个格子更新一次就会正确,那么就可以直接获取答案

#include <bits/stdc++.h>
using namespace std;
int ans,inf,a[105][105],f[105][105];
int main(){
	int n,m;cin>>n>>m;
	for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++){
		cin>>a[i][j];
		if(a[i][j])f[i][j]=min(min(f[i-1][j],f[i][j-1]),f[i-1][j-1])+1;
		if(f[i][j]!=inf)ans=max(ans,f[i][j]);
	}
	cout<<ans;
}

        

        

最大正方形 2

 

思路: 

如果我们设置f[i][j]表示<i,j>为右下角的最大正方形,因为不清楚<i,j>是0还是1,所以不好转移。

所以设置f[i][j][1]表示<i,j>=1为右下角的最大正方形边长,f[i][j][0]表示<i,j>=0

f[i][j][1]=min(min(f[i-1][j][0],f[i][j-1][0]),f[i-1][j-1][1])+1;

f[i][j][0]=min(min(f[i-1][j][1],f[i][j-1][1]),f[i-1][j-1][0])+1;

因为每个格子更新一次就会正确,那么就可以直接获取答案

#include <bits/stdc++.h>
using namespace std;
int ans,a[1505][1505],f[1505][1505][2];
int main(){
	int n,m;cin>>n>>m;
	for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++){
		scanf("%d",&a[i][j]);
		if(a[i][j]) f[i][j][1]=min(min(f[i-1][j][0],f[i][j-1][0]),f[i-1][j-1][1])+1;
		else f[i][j][0]=min(min(f[i-1][j][1],f[i][j-1][1]),f[i-1][j-1][0])+1;
		ans=max(ans,max(f[i][j][0],f[i][j][1]));
	}
	cout<<ans;
}

        

        

奶牛比赛 

 思路:

本题挺考思维的,首先要明确题意:我们可以确定这个点的排名,当且仅当它和其余所有点的关系都已经知道了。

我们知道floyd可以通过求最短路来判断两点之间连通性,那么当我们设置f[i][j]为1表示i和j之前有关系,如果u点和任意点都有关系,那么u点就算确定了。

为什么呢?

样例一
5 6
5 2
2 3
2 4
2 1
3 4
4 1
样例二
5 6
5 2
2 3
2 4
2 1
3 4
1 4

左边是样例一:你会发现5点能到其余点,那么它和其余点的关系就确定了,2能到3,4,1而5能到2,那么2和其余点的关系也确定了……

右边是样例而:你发现5点2点都可以确定,对于3点,它无法到1而1也无法到3,那么3点是不确定的。懂了吗? 

那么剩下的就是对floyd进行一下小修改:

设置f[i][j]表示<i,j>可以到达(i->j或j->i都行)  那么有

f[i][j]=f[i][j]||f[i][k]&&f[k][j](||表示只要有一个联通方式即可)

#include <bits/stdc++.h>
using namespace std;
int f[200][200];
int n,m,ans=0;
int main()
{
    cin>>n>>m;int x,y;
    for(int i=1;i<=m;++i){
		cin>>x>>y;f[x][y]=1;
    }
    for(int k=1;k<=n;++k)
      for(int i=1;i<=n;++i)
        for(int j=1;j<=n;++j)
        f[i][j]=f[i][j] || (f[i][k] && f[k][j]);
    for(int i=1;i<=n;++i){
        int a=1;
        for(int j=1;j<=n;++j){
            if(i==j) continue;//一定要忽略对角线
            else a=a && (f[i][j] || f[j][i]);//千万不要忘了还有f[j][i]
        }
        ans+=a;
    }
    printf("%d\n",ans);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值