动态规划-线性动态规划总结

浪尽此生 提交于 2020-02-12 03:48:34

线性规划个人理解就是给你一串数,可能是一个数列,可能是一个环,根据题意和数据递推出一个最优解。
核心过程就是能找到递推方程。
例如求一个数列的最长不上升子序列的长度。注意是长度不是序列!
数列:389 207 155 300 299 170 158 65 建一个dp数组,设这个数列储存在a数组
通常求什么,设什么,设dp[i]表示长度为i的串,最长不上升子序列的长度
dp[1]=a[1]=389
下一个数 a[2]=207 加在后面 dp[2]=207
下一个数a[3]=155 加在后面 dp[3]=155
下一个数 300 不能直接加在后面了,需要做选择 因为要最长不上升,可以知道末尾的数为数列最小数,我们希望最小数前面尽量大,这样是最好的情况
我们想要389 207 155 还是 389 300 155?
肯定是后面的,我们希望数下降的慢一些。

递推方式

if(这个数a[i]<dp[i]) 加在后面 dp[++len]=a[i]
else 遍历dp[]数组,更换更优的值(注意这里只是更换!没有增加长度)

例题一例题链接

题目描述
某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。

输入导弹依次飞来的高度(雷达给出的高度数据是 \le 50000≤50000的正整数),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。

输入格式
1行,若干个整数(个数 \le 100000≤100000)

输出格式
2行,每行一个整数,第一个数字表示这套系统最多能拦截多少导弹,第二个数字表示如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。

输入输出样例
输入
389 207 155 300 299 170 158 65
输出
6
2
解题思路
1.很明显,第一问求最多拦截到导弹数目,就是求最长不上升子序列的长度
但方法有两种,一种手写遍历dp数组,一种用c++的函数,建议函数,因为更快
2.第二问比较难想,也就是求不上升子序列的个数,可以转化成求最长上升子序列的长度。
原因:第一个不上升子序列里拿出一个a,肯定在第二个不上升子序列里找到一个大于a的b(如果找不到,为什么不把第二个接到第一个后面呢?想一下就懂了),同理在第三个序列找到c大于b。。。形成了a<b<c<d…就是一个最长上升子序列。最长上升子序列的每个数,一定是由n个降序子序列里的一个个选拔出来的最优情况
升序降序长度如何求,就不细说了
AC代码

#include<bits/stdc++.h>
using namespace std;
int dp1[100001];int a[100001];int dp2[100001];
//bool cmp(const int& a,const int& b){return a > b;}
int main(){
	int m=0;
    while(cin>>a[++m]){
    	if(getchar()=='\n') break;  
    }m--;
	int len1=1,len2=1;
	dp1[1]=a[1];dp2[1]=a[1];
	for(int i=2;i<=m;i++){
		if(dp1[len1]>=a[i]) dp1[++len1]=a[i];
		else{  
			int p1=upper_bound(dp1+1,dp1+1+len1,a[i],greater<int>())-dp1;
			dp1[p1]=a[i];
		}
		if(dp2[len2]<a[i]) dp2[++len2]=a[i];
		else{
			int p2=lower_bound(dp2+1,dp2+len2+1,a[i])-dp2;
			dp2[p2]=a[i];
		}
	}
	cout<<len1<<endl<<len2; 
} 

例题二.P1091 合唱队形题目链接

输入就是,给n个人,每个人的身高,想要找一个队形,先升后降最长
输出,除了队形里的人,需要剔除多少人
输入8
186 186 150 200 160 130 197 220
输出
4
解题思路
上一题就是求升序降序长度,用在这就可以了,把每个人的遍历一遍,以这个人为中心,队形多长。队形长度等于左边升序长度加右边降序长度。找出最大,用总人数-队形长度即可
AC代码

#include<bits/stdc++.h>
using namespace std;
int l[101],r[101],a[101];int n;
bool cmp(const int& a1,const int& b1){return a1 > b1;}
int uplen(int right){
	int dp[101]={0},len1=1;
	dp[1]=a[1];
	for(int i=2;i<=right;i++){
		if(dp[len1]<a[i]) dp[++len1]=a[i];
		else{
			int p1=lower_bound(dp+1,dp+1+len1,a[i])-dp;
			dp[p1]=a[i];
		}
	}
	return len1;
}//求左边升序长度
int downlen(int left){
	int dp[101]={0},len2=1;
	dp[1]=a[left];
	for(int i=left;i<=n;i++){
		if(dp[len2]>a[i]) dp[++len2]=a[i];
		else{
			int p2=lower_bound(dp+1,dp+1+len2,a[i],cmp)-dp;
			dp[p2]=a[i];
		}
	}
	return len2;
}//求右边降序长度
int main(){
	cin>>n;
	int max=0;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		l[i]=1;
		r[i]=1;
	} 
	//每个人都算一下 
	for(int i=1;i<=n;i++){
		l[i]=uplen(i);
		r[i]=downlen(i);
		if(l[i]+r[i]-1>max) {
			max=l[i]+r[i]-1;
		}
	}
	cout<<n-max;
}

例题三

P1280 尼克的任务链接

题意略
技巧就是,从前向后选择任务不可行,因为你每选择一件事情,需要考虑后面的情况,倒着先可以行得通。
方程
时间点从后往前 dp[i]表示时间i到结束的最大空闲时间,a[i]表示时间i开始的任务进行多久

if(该时间点无任务) dp[i]=dp[i+1]+1
else(有任务) dp[i]=max(dp[i],dp[i+a[i]])

AC代码

#include<bits/stdc++.h>
using namespace std;
struct p{
	int s,len;
};//每个任务的开始时间和工作时间长度
p a[10001];//储存数据
int b[10001],dp[10001];//dp[i]表示i-n的最大空闲时间
int ti,n,k=1;
int cmp(p a,p b) {
	return a.s>b.s;
}//将任务倒过来,当然也可以不排序,用最后一个任务下标表示k,把k++改为k--就行了
int main(){
	cin>>ti>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i].s>>a[i].len;
		b[a[i].s]++;
	}
	sort(a+1,a+n+1,cmp);
	for(int i=ti;i>=1;i--){
		if(b[i]==0){
			dp[i]=dp[i+1]+1; cout<<"!\n";
		}
		else{
			for(int j=1;j<=b[i];j++){
				dp[i]=max(dp[i],dp[i+a[k].len]);
				k++;
			}
		}
		for(int s=1;s<=15;s++){
			cout<<dp[s]<<" ";
		}
		cout<<endl;
	}
	cout<<dp[1];
}

例题四链接

P1880 [NOI1995]石子合并
思路
肯定要使用区间DP
花费代价如何计算
cost[i][j]表示i-j的和

for(int i=1;i<=n;i++)
	for(int j=i;j<=2*n;j++)
	for(int k=i;k<=j;k++){
		cost[i][j]+=a[k];
	}//i,j控制区间,k逐个给区间的值加起来,这是链的代价方法,环的话把n*2

区间dp大概的模板

每个区间都有一个k进行分割,割出区间最优解
for len=1...n-1 //表示区间的长度从1到n-1
      forint i=1,j=i+len;i<n,j<n;i++,j=i+len)//开始递推,1-2区间,2-3区间...
         dp[i][j] 赋初值;
         for(int k=i;k<j;k++)
             dp[i][j]=max||min(dp[i][j],dp[i][k]+dp[k+1][j]+cost[i][j]);

方程
dp[i][j]表示i-j区间的合并的最大成绩 cost[i][j]表示i-j区间所有石子数和,也就是说位的花费代价
求最大:dp[i][j]=max(dp[i][j],dp[i][i+k]+dp[k+1][j]+cost[i][j])
区间dp入门,可以看一下
AC代码

#include<bits/stdc++.h>
using namespace std;
int n;int minl=99999999,maxl=0;
int cost[201][201],a[201],dp1[201][201],dp2[201][201];
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		a[i+n]=a[i];
	}
	for(int i=1;i<=2*n;i++)
	for(int j=i;j<=2*n;j++)
	for(int k=i;k<=j;k++){
		cost[i][j]+=a[k];
	}
	//r表示搜的空间长度 
	for(int r=1;r<n;r++){
		for(int i=1,j=r+i;i<2*n,j<2*n;i++,j=i+r){
			dp2[i][j]=99999999;//找最小代价 
			for(int k=i;k<j;k++){
				dp1[i][j]=max(dp1[i][j],dp1[i][k]+dp1[k+1][j]+cost[i][j]);
				dp2[i][j]=min(dp2[i][j],dp2[i][k]+dp2[k+1][j]+cost[i][j]);
			}
		}
	}
	for(int i=1;i<=n;i++){
		maxl=max(maxl,dp1[i][i+n-1]);
		minl=min(minl,dp2[i][i+n-1]);
	}
	printf("%d\n%d",minl,maxl);
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!