讲义 GDFZOJ 【38】 动态规划基础3

更好的阅读体验

上楼梯问题

这是一个很水的题目,就是一个斐波那契数列……

设有 \(f\) 数组,则 \(f_i=f_{i-1}+f_{i-2}(f_1=1,f_2=2)\)

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=35;
int f[N]={0,1,2};
int main() {
	int n;
	scanf("%d",&n);
	for(int i=3; i<=n; i++) {
		__________
	}
	__________
	return 0;
}

乘车与购票

如果要抵达距离为 \(x\) 的一个点,可以从 \(x-y\) 的地方在乘一辆距离为 \(y\) 的车。

于是有状态转移方程:\(f_i=\max\{f_{i-j}+a_j\}\) 。其中 \(a\) 数组为 \(10\) 公里及以内的车费。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=105;
int f[N],a[N];
int main() {
	________
	for(int i=1; i<11; i++) {
		scanf("%d",a+i);
	}
	int n;
	scanf("%d",&n);
	for(int i=1; i<=n; i++) {
		for(int j=1; j<11; j++) {
			________        	
		}
	}
	printf("%d",f[n]);
	return 0;
}

小猪过河

我们可以让这只小猪从 \(0\) 号节点开始跳,跳到 \(n+1\) 号节点。

则有方程:$ f_i=\max(f_i,f_{i-j}-q+a_i),1\le j\le2$ 。

再判断一下在跳的过程中的体力值即可。

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int f[N],a[N];
int main() {
	int p,q;
	scanf("%d %d",&p,&q);
	int n;
	scanf("%d",&n);
	for(int i=1; i<=n; i++) {
		scanf("%d",a+i);
	}
	f[0]=p,f[1]=p-q>=0?p-q+a[1]:-1e9;
	for(int i=2; i<=n+1; i++) {
		int flag=0;
		________
	}
	if(f[n+1]<=0) {
		printf("NO");
	} else {
		printf("%d",f[n+1]);
	}
	return 0;
}

排队买票

我们可以发现:一个人可以自己买票,也可以和他后面的人买票。

所以有方程:\(f_i=\max(f_{i-1}+a_i,f_{i-1}+b_{i-1})\)

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int f[N],a[N],b[N];
int main() {
	int n;
	scanf("%d",&n);
	for(int i=1; i<=n; i++) {
		scanf("%d",a+i);
	}
	for(int i=1; i<n; i++) {
		scanf("%d",b+i);
	}
	________
	printf("%d",f[n]);
	return 0;
}

护卫队

\(f_i\) 为前 \(i\) 辆车通过桥的最短时间,设 \(t_{i,j}\)\(i\) -第 \(j\) 辆车(租车的车队)通过所需时间。

我们分析方程:

来源

综合起来,我们就得到了方程:\(f_i=\min(f_{j-1}+t_{j,i})\)且要满足 \(\sum_{k=j}^iw_k\le t\)

为了降低时间复杂度、编程复杂度,我们可以:

  1. 预处理重量

    定义 \(W_i\) 为前 \(i\) 辆车的总重(即前缀和,是处理这类问题非常好的方法,可以降低时间复杂度)。需要知道第 \(j\) 辆车到第 \(i\) 辆车的总重时,用\(W_i-W_{j-1}\)就可以了;

  2. 预处理时间

    定义 \(t\) 为桥的全长; \(v_i\) 为第 \(i\) 辆车的速度; \(T_i\) :第 \(i\) 辆车通过桥所需时间; \(t_{i,j}\)\(i\) 辆车到第 \(j\) 辆车组成的车队通过桥所需的时间;

  3. \(w\) 数组、\(v\) 数组和\(W\) 数组一定要开 long long,否则两个较大的 int 相加会炸掉! (虽然此题可能没有卡 int)。

代码:

#include<bits/stdc++.h>
using namespace std;
using LL=long long;
const LL N=1005;
LL w[N],s[N],sum[N];
double f[N],T[N],a[N][N];
int main() {
	LL t,l,n;
	scanf("%lld %lld %lld",&t,&l,&n);
	for(LL i=1; i<=n; i++) {
		scanf("%lld %lld",w+i,s+i),sum[i]=sum[i-1]+w[i],a[i][i]=T[i]=(double)l/s[i]*60;
	}
	for(LL i=1; i<=n-1; i++) {
		for(LL j=i+1; j<=n; j++) {
			a[i][j]=max(a[i][j-1],T[j]);
		}
	}
	for(LL i=1; i<=n; i++) {
		f[i]=T[i]+f[i-1];
		________
   	}
	printf("%.1lf",f[n]);
	return 0;
}

工作安排

\(f_i\) 为前 \(i\) 分钟能获得的最大的经验值。

默认在工作后才能获得经验值。容易知道,对于每一个工作,完成时间的 \(f\) 值就等于开始时间的 \(f\) 值加上工作所获得的经验值(里面取个最大值),即 \(f_{S_i+E_i}=max(f_{S_i+E_i},f_{S_i}+P_i)\)

但是要注意,对于第 \(i\) 分钟,它可能不是某个工作的结束时间,即 \(f_i\) 不会被更新。所以在枚举完前一个的结束状态时,就要用 \(f_j=\max(f_j,f_{j-1})\) 对结果进行递推。

#include<bits/stdc++.h>
using namespace std;
const int N=5005;
struct work {
	int s,e,p;
	void read() {
		scanf("%d %d %d",&s,&e,&p);
		e+=s;
	}
	bool operator<(const work &x) const {
		________
	}
} a[N];
int f[N];
int main() {
	int n,m;
	scanf("%d\n%d",&m,&n);
	for(int i=1; i<=n; i++) {
		a[i].read();
	}
	sort(a+1,a+n+1);
	________
	int ans=0;
	for(int i=1; i<=m; i++) {
		ans=max(ans,f[i]);
	}
	printf("%d",ans);
	return 0;
}

最长上升子序列

首先我们来讲解一下他的递推关系式:\(f_i=\max(f_i,f_j+1)\)

定义 \(f_i\) 为:以 \(a_i\) 为末尾的最长上升子序列的长度。

那么 \(f_i\) 包含什么呢?

情况\(1\):只包含它自己,也就是说它前面的元素全部都比他大;

情况2:为了保证上升子序列尽可能的长,那么就有 \(f_i\) 尽可能的大,但是再保证 \(f_i\) 尽可能大的基础上,还必须满足序列的上升。所以 \(f_i=\max(1,f_j+1)(j<i,a_j<a_i)\) 。这里的 \(1\) 就是当 \(a_i\) 前面的数都比他大的时候,他自己为一个子序列;\(f_j+1\) 指的是: 当第 \(i\) 个数前面有一个第 \(j\) 个数满足 \(a_j<a_i\) 并且 \(j<i\) 这时候就说明 \(a_i\) 元素可以承接在 \(a_j\) 元素后面来尽可能的增加子序列的长度。

\(j\)\(1\) 遍历到 \(i-1\) ,在这之间,找出尽可能大的 \(dp_i\)即为最长上升子序列的长度。(注意: \(f_n\) 不一定是最长的子序列。)

来源

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1005;
int a[N],f[N];
int main() {
	int n;
	scanf("%d",&n);
	for(int i=1; i<=n; i++) {
		scanf("%d",a+i),f[i]=1;
	}
	int ans=0;
	________
	printf("%d",ans);
	return 0;
}

合唱队形

显然这是一个最长上升子序列最长下降子序列有机结合。

求出两者可连接的数量的最大值,那 \(n\) 去减即可。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=205;
int a[N],f[2][N];
int main() {
	int n;
	scanf("%d",&n);
	for(int i=1; i<=n; i++) {
		scanf("%d",a+i),f[1][i]=f[0][i]=1;
	}
	________
	int ans=0;
	for(int i=1; i<=n; i++) {
		ans=max(f[0][i]+f[1][i]-1,ans);
	}
	printf("%d",n-ans);
	return 0;
}

友好城市

将两岸的友好城市用一个结构体存起来,以其中一个城市作为关键字进行排序,那么之后另一个城市其实会呈最长上升子序列,例如 \(A\) 岸城市 \(1\)\(A\)岸城市 \(1\) 的意思是距离河的起点为 \(1\)\(A\) 岸城市)和 \(B\) 岸城市 \(3\) 之间如果开通航线,那么以后的城市都不可能向 \(B\) 岸城市 \(1\)\(2\)\(3\) 开通航线。那么也就呈最长上升子序列状,只要套用模板求出最长即可。

注意:一定要排序

来源

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=5005;
struct line {
	int c1,c2;
	________
	void read() {
		scanf("%d %d",&c1,&c2);
	}
} a[N];
int f[N];
int main() {
	int x,y;
	scanf("%d %d",&x,&y);
	int n;
	scanf("%d",&n);
	for(int i=1; i<=n; i++) {
		a[i].read(),f[i]=1;
	}
	sort(a+1,a+n+1);
	________
	int ans=0;
	for(int i=1; i<=n; i++) {
		ans=max(ans,f[i]);
	}
	printf("%d",ans);
	return 0;
}

后记

  1. 感谢 L_Z_Y 对此文章提出的建议,并帮助我写了 工作安排 这一块!

  2. 本文有彩蛋

  3. 本文由 Z_Z_R 编写,zhnzh 转载与此。

posted @ 2020-08-04 14:04  zhnzh  阅读(104)  评论(0编辑  收藏  举报