初级动态规划

本文探讨了如何解决三个实际问题:最大和上升子序列中的收益优化、任务执行策略以获取最大收益、以及合理分配苹果以获取积分最大化,同时涉及了队列问题中不同排列的计数。通过递归和优先队列算法,展示了如何利用策略优化资源利用和时间安排。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

最大和上升子序列

执行任务

任务1

任务2

有几种方案?

数的划分

放苹果

排队问题


最大和上升子序列

样例输入

 6
 3 7 4 2 6 8

样例输出

 21

思路: 按样例即 3 4 6 8  

思路就是:从 i 向 j 跳

#include<bits/stdc++.h>
using namespace std;
int main(){
	int n;scanf("%d",&n);
	int a[1005];
	int f[1005]; // 以下标为i的数作为结尾的子序列最大和为f[i] 
	for(int i = 1;i<=n;++i){
		scanf("%d",&a[i]);
		f[i] = a[i];
	}
    // 从 i 向 j 跳 
	for(int i = 1;i<=n;++i){
		for(int j = i+1;j<=n;++j){
			if(a[j]>a[i]){
				f[j] = max(f[j],f[i]+a[j]);
			}
		}
	}
	
	int ans = 0;
	for(int i =1;i<=n;++i){
		ans = max(ans,f[i]);
	}
	printf("%d\n",ans);
	
	return 0;
}

执行任务

任务1

你有n个任务,其中第i个任务,在si开始,ei时刻结束,如果做这个任务,你能获得wi的收益。

但是你在一个时刻只能做一个任务,问选择哪些任务,能让你的收益尽量大。

注意:你在上一个任务结束后马上开始下一个任务是可以的。

样例输入

 3
 1 3 100
 2 4 199
 3 5 100

样例输出

 200

ei 最大值为1000

思路:任务只能在规定的时段开始,从最开始枚举时间。 遍历所有的任务,找到所有能从该时段开始执行的任务

#include<bits/stdc++.h>
using namespace std;
const int mod = 1e9+7;
int n; 
int f[1005];
int main(){
	cin>>n;
	int a[1005],b[1005],w[1005];
	for(int i = 1;i<=n;++i)scanf("%d %d %d",&a[i],&b[i],&w[i]);
	
	// f[i]: i时刻最大的收益 
	for(int i = 1;i<=1000;++i){
		f[i] = max(f[i],f[i-1]);// i时刻 不休息和休息 
		for(int j = 1;j<=n;++j){
			if(a[j]==i){
				f[b[j]]=max(f[b[j]],f[i]+w[j]);
			}
		}
	}
	printf("%d\n",f[1000]);
	return 0;
}

任务2

Kimi同学最近迷上了一款通过执行一系列小任务来获取积分的游戏。当然,这些积分是可以兑换礼物的。
在该游戏中,每一个小任务都需要10分钟时间来完成,且每个小任务都设有一个截止时间,任务的结束时间不能大于所设的截止时间。如果没有在规定的截止时间内完成,则任务失败。每成功完成一个小任务都可以获得一定的积分,每个小任务所对应的积分值不一定相同。
现在给出每一个小任务的截止时间和完成该任务所获得的积分值。你能否编写一个程序计算出Kimi最多可以获得的积分为多少?

样例输入 

5
20 5
30 10
25 8
15 15
10 6

样例输出 

33

分析:这个是可以把任务拆分开来做的,比如先做5分钟任务1,再做10分钟任务2,再回过头去做任务1。

思路:可以以最后的截止时间向前完成任务。但一个时段只能完成一个任务,如果一个时段有多个任务的话,完成回报最高的任务,其他的任务堆积下来,看看能不能在时间向前推进时完成。

为什么要从最后的截止时间向前推进呢? 因为如果是从一开始向后推进的话:到了截止时间,即使有起始的任务堆积下来,那就不能完成了。而如果是从后往前的话,堆积下来的任务是还没到截止时间的。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1005;
priority_queue<int>p;
queue<int>q[N];
ll ans;
int i;
int n,x,y;
int main(){
    scanf("%d",&n);
    while(n--){
        scanf("%d%d",&x,&y);
        q[x/10].push(y);
        i=max(i,x/10);//最大时间段
    }
    do{
        while(!q[i].empty()){
            p.push(q[i].front());
            q[i].pop();
        }
        // 一个时段只做一件事 
        if(!p.empty()){
            ans+=p.top();
            p.pop();
        }
    }while(--i);
    printf("%lld\n",ans);
    return 0;
}

有几种方案?

数的划分

题目描述

使用递归编写一个程序,求一个正整数n的所有划分个数。 例如,输入3,输出3;输入4,输出5。

样例输入 

 3
 4

样例输出 

 3     
 5     

分析:

3 : 111 12 3

4 : 1111 112 13 4 22

思路:

设 f[ i ][ j ]的含义为: 将 i 分成 j 份

i分成j份,可分为两种情况:

1、这 j 份里,至少有一个1

2、全集减去第一种情况,那就是这 j 份 全部都是 2 及以上。都是2以上的话,将这 j 份全都减去一个 1 是不影响 结果的。

所以,递推公式为:

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

#include<bits/stdc++.h>
using namespace std;
#define ll long long
int n;
int f[1005][1005];
int main() {
    while(~scanf("%d",&n)){
    	memset(f,0,sizeof(f));
    	f[0][0] = 1; // 0分成0份也是一种分法 
    	// f[i][j] 把 i 划分成 j 份的分法 
    	for(int i = 1;i<=n;++i){
    		for(int j = 1;j<=n;++j){
    			if(i<j)f[i][j] = 0;
    			// j份里至少有1份1,或者每份都大于等于2 
    			else f[i][j] = f[i-1][j-1]+f[i-j][j];
    		}
    	}
    	int ans = 0;
    	for(int i = 1;i<=n;++i){
    		ans += f[n][i];
    	}
    	printf("%d\n",ans);
    }
    return 0;
}

放苹果

分析:与数的划分是一样的,只不过数是大于等于1的,而盘子是可以为空的

数的划分:

①  至少有一个1 

②  全都是2以上

放苹果:

① 至少有一个是空盘

② 每个盘子都有一个苹果

#include <bits/stdc++.h>
#define ll long long
using namespace std;
int work(int m,int n){// m个苹果放进 n 个盘子 
	if(m==0||n==1)return 1; // m=0 :没有苹果了,是一种放法  
    // n=1 : 只有一个盘子,那只能把所有苹果都放在这个盘子上,也是只有一种放法
	if(m<n)return work(m,m);
	else return work(m,n-1)+work(m-n,n);
} 
int main(){
    int m,n;
	while(~scanf("%d %d",&m,&n)){ // 如果是多组输入,可使用记忆化搜索
		printf("%d\n",work(m,n));
	}
    return 0;
}

排队问题

X星最近来了一位超人气魔术师,火爆之极,导致魔术的现场表演活动一票难求。
已知魔术现场表演活动的门票需要1X星币,现在有N个(N为偶数)X星人前往购买,其中刚好有一半人(N/2)携带了一个面值为1的X星币,还有一半人(N/2)携带了一个面值为2的X星币。
由于工作的疏忽,魔术现场表演活动的售票处在购票之前居然没有钱,对,忘记准备零钱了。
你能否编写一个程序,统计这N个X星人有多少种不同的排队方式,可以让售票处避免出现没有钱找的尴尬局面。

反思:当时想的是,新增了两个人,那假设这两个人都排在末尾,那就是 f[i] = f[i-2] 了,还有1可以插入到前面去,该怎么插呢...一直在想...

分析:

设 f[ i ][ j ]的含义为: 这个队伍有 i 个 1元的人, j 个 2 元的人,满足条件的个数。

所以 f [ i ][ j ] = f [ i - 1 ][ j ] + f[ i ][ j-1 ]    

即,拿1元的在末尾,或者拿2元的站末尾。

这题的数据是说 m = n ,其实也可以有不用两数相等。

#include<bits/stdc++.h>
using namespace std;
#define ll long long 
ll dp[35][35];
int main(){
	dp[0][0] = 1;
	for(int i = 0;i<=30;++i){
		for(int j = 0;j<=30;++j){
			if(i<j)dp[i][j] = 0;
			else if(j==0)dp[i][j] = 1;
			else dp[i][j] = dp[i-1][j]+dp[i][j-1];
		}
	}
	int n;scanf("%d",&n);
	printf("%lld",dp[n>>1][n>>1]);	
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xingxg.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值