洛谷:线性动态规划专题练习

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

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

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

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

输入输出样例
输入 #1 复制
389 207 155 300 299 170 158 65
输出 #1 复制
6
2
说明/提示
为了让大家更好地测试n方算法,本题开启spj,n方100分,nlogn200分

每点两问,按问给分
代码:

#include<iostream>
#include<algorithm>
using namespace std;
const int N=100010;
int a[N],d1[N],d2[N],n;
int main() {
    while(cin>>a[++n]);n--;     //输入
    int len1=1,len2=1;      //初始长度为1
    d1[1]=a[1];     //用于求不上升序列长度
    d2[1]=a[1];     //用于求上升序列长度
    for(int i=2; i<=n; i++) {       //从a[2]开始枚举每个数(a[1]已经加进去了)
        if(d1[len1]>=a[i])d1[++len1]=a[i];      //如果满足要求(不上升)就加入d1
        else {      //否则用a[i]替换d1中的一个数
            int p1=upper_bound(d1+1,d1+1+len1,a[i],greater<int>())-d1;
            d1[p1]=a[i]; 
        }
        if(d2[len2]<a[i])d2[++len2]=a[i];       //同上
        else {
            int p2=lower_bound(d2+1,d2+1+len2,a[i])-d2;
            d2[p2]=a[i];
        }
    }
    cout<<len1<<endl<<len2;     //输出
    return 0;       //结束
}

题目描述
NN位同学站成一排,音乐老师要请其中的(N-KN−K)位同学出列,使得剩下的KK位同学排成合唱队形。

合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1,2,…,K1,2,…,K,他们的身高分别为T_1,T_2,…,T_KT
1
​ ,T
2
​ ,…,T
K
​ , 则他们的身高满足T_1<…<T_i>T_{i+1}>…>T_K(1 \le i \le K)T
1
​ <…<T
i
​ >T
i+1
​ >…>T
K
​ (1≤i≤K)。

你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。

输入格式
共二行。

第一行是一个整数N(2 \le N \le 100)N(2≤N≤100),表示同学的总数。

第二行有nn个整数,用空格分隔,第ii个整数T_i(130 \le T_i \le 230)T
i
​ (130≤T
i
​ ≤230)是第ii位同学的身高(厘米)。

输出格式
一个整数,最少需要几位同学出列。

输入输出样例
输入 #1 复制
8
186 186 150 200 160 130 197 220
输出 #1 复制
4
说明/提示
对于50%的数据,保证有n \le 20n≤20;

对于全部的数据,保证有n \le 100n≤100。
思路:先从1到n求一趟最长升,然后从n到1也求一趟,最后枚举中间的Ti,然后从众多Ti中挑个大的。

#include<bits/stdc++.h>
using namespace std;
const int maxn=200;
int a[maxn],n,ans,f[2][maxn];
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
	}
	a[0]=0;
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<i;j++)
		{
			if(a[i]>a[j])
			{
				f[0][i]=max(f[0][i],f[0][j]+1);
			}
		}
	}
	a[n+1]=0;
	for(int i=n;i;i--)
	{
		for(int j=n+1;j>i;j--)
		{
			if(a[i]>a[j])
			{
				f[1][i]=max(f[1][i],f[1][j]+1);
			}
		}
	}
	for(int i=1;i<=n;i++)
	{
		ans=max(f[0][i]+f[1][i]-1,ans);
	}
	cout<<n-ans<<endl;
	return 0;
}

题目描述
尼克每天上班之前都连接上英特网,接收他的上司发来的邮件,这些邮件包含了尼克主管的部门当天要完成的全部任务,每个任务由一个开始时刻与一个持续时间构成。

尼克的一个工作日为N分钟,从第一分钟开始到第N分钟结束。当尼克到达单位后他就开始干活。如果在同一时刻有多个任务需要完成,尼克可以任选其中的一个来做,而其余的则由他的同事完成,反之如果只有一个任务,则该任务必需由尼克去完成,假如某些任务开始时刻尼克正在工作,则这些任务也由尼克的同事完成。如果某任务于第P分钟开始,持续时间为T分钟,则该任务将在第P+T-1分钟结束。

写一个程序计算尼克应该如何选取任务,才能获得最大的空暇时间。

输入格式
输入数据第一行含两个用空格隔开的整数N和K(1≤N≤10000,1≤K≤10000),N表示尼克的工作时间,单位为分钟,K表示任务总数。

接下来共有K行,每一行有两个用空格隔开的整数P和T,表示该任务从第P分钟开始,持续时间为T分钟,其中1≤P≤N,1≤P+T-1≤N。

输出格式
输出文件仅一行,包含一个整数,表示尼克可能获得的最大空暇时间。

输入输出样例
输入 #1 复制
15 6
1 2
1 6
4 11
8 5
8 1
11 5
输出 #1 复制
4
思路:(本时刻无任务)f[i]=f[i+1]+1;//继承上一个时刻的最大空闲时间后+1

(本时刻有任务)f[i]=max(f[i],f[i+a[sum])//a[sum]表示在这个时刻的任务的持续时间,找出选择哪一个本时刻任务使空闲时间最大化
代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn =1e4+66;
struct node{
	int begin,time;
}a[maxn];
int sum[maxn];
int f[maxn];
int n,k;
int num=1;
bool cmp(const node &a,const node &b)
{
	return a.begin>b.begin;
}
int main()
{
	cin>>n>>k;
	for(int i=1;i<=k;i++)
	{
		cin>>a[i].begin>>a[i].time;
		sum[a[i].begin]++;
	}
	sort(a+1,a+1+k,cmp);
	for(int i=n;i>=1;i--)
	{
		if(sum[i]==0)
		{
			f[i]=f[i+1]+1;
		 }
		 else
		 {
		 	for(int j=1;j<=sum[i];j++)
		 	{
		 		f[i]=max(f[i],f[i+a[num].time]);
		 		num++;
			 }
		  } 
	}
	cout<<f[1]<<endl;
	return 0;
	
}

题目描述
在一个圆形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。

试设计出1个算法,计算出将N堆石子合并成1堆的最小得分和最大得分.

输入格式
数据的第1行试正整数N,1≤N≤100,表示有N堆石子.第2行有N个数,分别表示每堆石子的个数.

输出格式
输出共2行,第1行为最小得分,第2行为最大得分.

输入输出样例
输入 #1 复制
4
4 5 9 4
输出 #1 复制
43
54
代码:

#include<iostream>  
#include<cstdio>  
#include<cmath>  
using namespace std;   
int n,minl,maxl,f1[300][300],f2[300][300],num[300];  
int s[300];  
inline int d(int i,int j){return s[j]-s[i-1];}  
//转移方程:f[i][j] = max(f[i][k]+f[k+1][j]+d[i][j];
int main()  
{   
    scanf("%d",&n);  
    for(int i=1;i<=n+n;i++) 
    {  
        scanf("%d",&num[i]);  
        num[i+n]=num[i];  
        s[i]=s[i-1]+num[i];  
    }  
    for(int p=1;p<n;p++)  
    {  
        for(int i=1,j=i+p;(j<n+n) && (i<n+n);i++,j=i+p)  
        {  
            f2[i][j]=999999999;  
            for(int k=i;k<j;k++)  
            {  
                f1[i][j] = max(f1[i][j], f1[i][k]+f1[k+1][j]+d(i,j));   
                f2[i][j] = min(f2[i][j], f2[i][k]+f2[k+1][j]+d(i,j));  
            }  
        }  
    }  
    minl=999999999;  
    for(int i=1;i<=n;i++)  
    {  
        maxl=max(maxl,f1[i][i+n-1]);  
        minl=min(minl,f2[i][i+n-1]);  
    }  
    printf("%d\n%d",minl,maxl);  
    return 0;  
}

题目背景
大家都知道,基因可以看作一个碱基对序列。它包含了44种核苷酸,简记作A,C,G,TA,C,G,T。生物学家正致力于寻找人类基因的功能,以利用于诊断疾病和发明药物。

在一个人类基因工作组的任务中,生物学家研究的是:两个基因的相似程度。因为这个研究对疾病的治疗有着非同寻常的作用。

题目描述
两个基因的相似度的计算方法如下:

对于两个已知基因,例如AGTGATGAGTGATG和GTTAGGTTAG,将它们的碱基互相对应。当然,中间可以加入一些空碱基-,例如:

这样,两个基因之间的相似度就可以用碱基之间相似度的总和来描述,碱基之间的相似度如下表所示:

那么相似度就是:(-3)+5+5+(-2)+(-3)+5+(-3)+5=9(−3)+5+5+(−2)+(−3)+5+(−3)+5=9。因为两个基因的对应方法不唯一,例如又有:

相似度为:(-3)+5+5+(-2)+5+(-1)+5=14(−3)+5+5+(−2)+5+(−1)+5=14。规定两个基因的相似度为所有对应方法中,相似度最大的那个。

输入格式
共两行。每行首先是一个整数,表示基因的长度;隔一个空格后是一个基因序列,序列中只含A,C,G,TA,C,G,T四个字母。1 \le 1≤序列的长度 \le 100≤100。

输出格式
仅一行,即输入基因的相似度。

输入输出样例
输入 #1 复制
7 AGTGATG
5 GTTAG
输出 #1 复制
14
思路:1.(特判)因为第一/二段的1与第二/一段的0匹配时可视作与空碱基匹配,所以先进行一波预处理:
2.接着逐个判断即可得出三条状态转移方程,取三种配对方法(1.一中插空碱基;2.二中插空碱基;3.直接配对)所得结果中最优的一种。

(上伪代码)

{ 若在第一段基因中插入空碱基相似度更优 则在第一段插入空碱基

若在第二段基因中插入空碱基相似度更优 则在第二段插入空碱基

若直接配对的相似度高于以上两者 则直接插入
代码:

#define N 105
#include<cstdio>
#include<string>
#include<iostream>
#define max(a,b) (a>b?a:b)
#define min(a,b) (a<b?a:b)
const int tab[5][5]=
{
    {5,-1,-2,-1,-3},
    {-1,5,-3,-2,-4},
    {-2,-3,5,-2,-2},
    {-1,-2,-2,5,-1},
    {-3,-4,-2,-1,0}
};
int la,lb;
std::string sa,sb;
int a[N],b[N];
int dp[N][N];
int main()
{
    std::ios::sync_with_stdio(0);
    std::cin>>la>>sa>>lb>>sb;
    for(int i=1;i<=la;i++) for(int j=1;j<=lb;j++) dp[i][j]=-2e8;
    for(int i=1;i<=la;i++)
    {
        if(sa[i-1]=='A') a[i]=0;
        if(sa[i-1]=='C') a[i]=1;
        if(sa[i-1]=='G') a[i]=2;
        if(sa[i-1]=='T') a[i]=3;
    }
    for(int i=1;i<=lb;i++)
    {
        if(sb[i-1]=='A') b[i]=0;
        if(sb[i-1]=='C') b[i]=1;
        if(sb[i-1]=='G') b[i]=2;
        if(sb[i-1]=='T') b[i]=3;
    }
    for(int i=1;i<=la;i++) dp[i][0]=dp[i-1][0]+tab[a[i]][4];
    for(int i=1;i<=lb;i++) dp[0][i]=dp[0][i-1]+tab[b[i]][4];
    for(int i=1;i<=la;i++)
          for(int j=1;j<=lb;j++)
        {
            dp[i][j]=max(dp[i][j],dp[i][j-1]+tab[b[j]][4]);
            dp[i][j]=max(dp[i][j],dp[i-1][j]+tab[a[i]][4]);
            dp[i][j]=max(dp[i][j],dp[i-1][j-1]+tab[a[i]][b[j]]);
        }
    printf("%d",dp[la][lb]);
    return 0;
}

多米诺骨牌有上下2个方块组成,每个方块中有1~6个点。现有排成行的

上方块中点数之和记为S1,下方块中点数之和记为S2,它们的差为|S1-S2|。例如在图8-1中,S1=6+1+1+1=9,S2=1+5+3+2=11,|S1-S2|=2。每个多米诺骨牌可以旋转180°,使得上下两个方块互换位置。 编程用最少的旋转次数使多米诺骨牌上下2行点数之差达到最小。

对于图中的例子,只要将最后一个多米诺骨牌旋转180°,可使上下2行点数之差为0。

输入格式
输入文件的第一行是一个正整数n(1≤n≤1000),表示多米诺骨牌数。接下来的n行表示n个多米诺骨牌的点数。每行有两个用空格隔开的正整数,表示多米诺骨牌上下方块中的点数a和b,且1≤a,b≤6。

输出格式
输出文件仅一行,包含一个整数。表示求得的最小旋转次数。

输入输出样例
输入 #1 复制
4
6 1
1 5
1 3
1 2
输出 #1 复制
1
思路:
题目要求的是最小差值情况下的最小交换次数,那么我们把其中一个计入状态里。记交换次数好像不太好做(我没试过),所以我们要记的是差值。

但是差值是一个绝对值,好像也不是很好表示,所以我们再来转化一下。观察到每次交换只是把上下两个数交换,故前i个骨牌上下两行数的总和是不变的,所以我们只需记录其中一行数字的和就可以知道差值了。这样状态就好表示了。

f[i][j]表示前i个数字,第一行的数字和是j时,最小的交换次数。初始值所有f[i][j]都是无穷大,f[1][a[1]]=0,f[1][b[1]]=1。(a[]和b[]分别表示第一行和第二行的数字)

转移时,枚举每一个可能的和,共有6*n个,考虑当前一个交不交换即可
代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn=1200;
const int INF=1e9;
int a[maxn];
int b[maxn];
int dp[maxn][6*maxn];
int main()
{
	int n;
	cin>>n;
	int s=0;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i]>>b[i];
		s+=a[i]+b[i];
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<=6*n;j++)
		{
			dp[i][j]=INF;
		}
	}
	dp[1][b[1]]=1;
	dp[1][a[1]]=0;
	for(int i=2;i<=n;i++)
	{
		for(int j=0;j<=6*n;j++)
		{
			if(j-a[i]>=0)
			{
				dp[i][j]=min(dp[i][j],dp[i-1][j-a[i]]);
			}
			if(j-b[i]>=0)
			{
				dp[i][j]=min(dp[i][j],dp[i-1][j-b[i]]+1);
			}
		}
	}
	int mind=INF,mint=INF;
	for(int i=0;i<=s;i++)
	{
		if(dp[n][i]!=INF)
		{
			if(abs(i-(s-i))<mind)
			{
				mind=abs(i-(s-i));
				mint=dp[n][i]; 
			}
			else if(abs(i-(s-i))==mind)
			{
				mint=min(mint,dp[n][i]);
			}
		}
	}
	cout<<mint<<endl;
	return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值