信息笔记——动态规划之基础(4)------例题(LIC)

导弹拦截 noip2000

题目描述:

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

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

输入格式:

1行,若干个整数(个数100000)

输出格式:

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

输入样例: 
389 207 155 300 299 170 158 65
输出样例: 
6
2
这道题第一问是明显的最长不上升子序列,如果用动态规划,时间复杂度是O(n^2)。
第二问是一道贪心问题。

我用的是递归版的,递推也可以做。
#include<iostream>
#include<cstdio>
using namespace std;
int n,d[10000]={},a[10000]={};
int dp(int i){
    int& ans=d[i];
    if(ans>0)return ans;
    ans=1;
    for(int j=i+1;j<n;j++)if(a[j]<=a[i])ans=max(ans,dp(j)+1);
    return ans;
}
int main(){
    cin>>n;
    for(int i=0;i<n;i++)cin>>a[i];
    int head[100]={a[0]},num=1,ans=0;
    for(int i=0;i<n;i++)ans=max(ans,dp(i));
    for(int i=0;i<n;i++){//贪心算法 
        bool ok=1;
        for(int j=0;j<num;j++){    
            if(head[j]>=a[i]){
                head[j]=a[i];
                ok=false;
                break;
            }
        }
        if(ok)head[num++]=a[i];
    }
    cout<<ans<<endl;
    cout<<num<<endl;
    return 0;
}
O(n^2)算法


合唱队形

题目描述

N位同学站成一排,音乐老师要请其中的(NK)位同学出列,使得剩下的K位同学排成合唱队形。

合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1,2,,K,他们的身高分别为T1,T2,,TK​,则他们的身高满足T1<...<Ti>Ti+1>>TK(1iK)。

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

输入格式:

共二行。

第一行是一个整数N(2N100),表示同学的总数。

第二行有n个整数,用空格分隔,第i个整数Ti(130Ti230)是第i位同学的身高(厘米)。

输出格式:

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

输入样例: 
8
186 186 150 200 160 130 197 220
输出样例: 
4
这道题是一个双向LIS,左边是正版LIS,右边是最长下降子序列。

注意看他问的是出队人数。
#include<iostream>
#include<cstdio>
using namespace std;
int n,t[1000]={},in[1000]={},de[1000]={},ans=999;
int main(){
    cin>>n;
    for(int i=1;i<=n;i++)cin>>t[i];
    for(int i=1;i<=n;i++){
        in[i]=1;
        for(int j=i-1;j>0;j--)if(t[i]>t[j])in[i]=max(in[i],in[j]+1);
    }
    for(int i=n;i>0;i--){
        de[i]=1;
        for(int j=i+1;j<=n;j++)if(t[i]>t[j])de[i]=max(de[i],de[j]+1);
    }
    for(int k=0;k<=n;k++)ans=min(ans,k-in[k]+(n-k+1)-de[k]);
    cout<<ans<<endl;
    return 0;
}
View Code

滑雪

描述
Michael喜欢滑雪百这并不奇怪, 因为滑雪的确很刺激。可是为了获得速度,滑的区域必须向下倾斜,而且当你滑到坡底,你不得不再次走上坡或者等待升降机来载你。Michael想知道载一个区域中最长的滑坡。区域由一个二维数组给出。数组的每个数字代表点的高度。下面是一个例子
 1  2  3  4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9
一个人可以从某个点滑向上下左右相邻四个点之一,当且仅当高度减小。在上面的例子中,一条可滑行的滑坡为24-17-16-1。当然25-24-23-...-3-2-1更长。事实上,这是最长的一条。
输入格式:
输入的第一行表示区域的行数R和列数C(1 <= R,C <= 100)。下面是R行,每行有C个整数,代表高度h,0<=h<=10000。
输出格式:
输出最长区域的长度。
样例输入:
5 5
1 2 3 4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9
样例输出:
25
这道题是二维的LIS,没什么好说的,多想一想就可以写代码。
另外,我个人认为,这道题,递归比递推方便。
#include<iostream>
#include<cstdio>
using namespace std;
int R,C,a[200][200]= {},d[200][200]= {};
int dp(int i,int j) {
    int &ans=d[i][j];
    if(ans>0)return ans;
    ans=1;
    if(i-1>0)
        if(a[i-1][j]<a[i][j])ans=max(ans,dp(i-1,j)+1);
    if(i+1<=R)
        if(a[i+1][j]<a[i][j])ans=max(ans,dp(i+1,j)+1);
    if(j-1>0)
        if(a[i][j-1]<a[i][j])ans=max(ans,dp(i,j-1)+1);
    if(j+1<=C)
        if(a[i][j+1]<a[i][j])ans=max(ans,dp(i,j+1)+1);
    return ans;
}
int main() {
    cin>>R>>C;
    for(int i=1; i<=R; i++)
        for(int j=1; j<=C; j++)cin>>a[i][j];
    int maxn=0;
    for(int i=1; i<=R; i++)
        for(int j=1; j<=C; j++)
            maxn=max(maxn,dp(i,j));
    cout<<maxn<<endl;
    return 0;
}
View Code
难解的问题

题目描述:

给定一个序列<a1,a2,...,an>.求最长上升子序列(lis)p1<p2<...<pw满足a[p1]<a[p2]<...<a[pw]
例如65 158 170 299 300 155 207 389
LIS=<65,158,170,299,300,389>。
但是,现在还有一个附加条件:求出的最长上升子序列必须含有第K项。
比如,在上面的例子中,要求求出的最长上升子序列必须含有第6项,那么最长上升子序列就是:65 155 207 389。

输入格式:

第一行是用空格隔开的两个正整数N、K,含义同上所述.
第二行N个数,即给出的序列.

输出格式:

仅有一个数,表示含有第K项的最长上升子序列的长度.

样例输入:

5 3
1 2 3 2 1

样例输出:

3
这道题有点绕,但是第k项作为为界限,两段分别LIS即可。
举个例子:1236257,k=4。
这个时候只要求出前4个数的LIS(即d[4]),加上后面三个的LIS,注意不是d[7],是所有的上升序列(注意包含第4个数)中最大值LIS。
这里就不给代码了,
提交地址在COGS。

3532:最大上升子序列和

描述:

一个数的序列bi,当b1 < b2 < ... < bS的时候,我们称这个序列是上升的。对于给定的一个序列(a1, a2, ...,aN),我们可以得到一些上升的子序列(ai1, ai2, ..., aiK),这里1 <= i1 < i2 < ... < iK <= N。比如,对于序列(1, 7, 3, 5, 9, 4, 8),有它的一些上升子序列,如(1, 7), (3, 4, 8)等等。这些子序列中序列和最大为18,为子序列(1, 3, 5, 9)的和.
你的任务,就是对于给定的序列,求出最大上升子序列和。注意,最长的上升子序列的和不一定是最大的,比如序列(100, 1, 2, 3)的最大上升子序列和为100,而最长上升子序列为(1, 2, 3)

输入:
输入的第一行是序列的长度N (1 <= N <= 1000)。第二行给出序列中的N个整数,这些整数的取值范围都在0到10000(可能重复)。
输出:
最大上升子序列和
样例输入:
7
1 7 3 5 9 4 8
样例输出:
18
这道题是变向考你LIS,只不过像带权的LIS(注意并不是)。
直接给代码。
#include<iostream>
#include<cstdio>
using namespace std;
int max(int a,int b){return a>b?a:b;}
int n,a[1000+10]={},d[1000+10]={};
int dp(int cur){
    int& ans=d[cur];
    if(ans>0)return ans;
    ans=a[cur];
    for(int i=cur+1;i<=n;i++)if(a[i]>a[cur])ans=max(ans,dp(i)+a[cur]);
    return ans;
}
int main(){
    cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i];
    int ans=0;
    for(int i=1;i<=n;i++)ans=d[i]>0?max(ans,d[i]):max(ans,dp(i));
    cout<<ans<<endl;
    return 0;
}
递归版
#include<iostream>
#include<cstdio>
using namespace std;
int max(int a,int b){return a>b?a:b;}
int main(){
    int n,a[1000+10]={},d[1000+10]={};
    cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=n;i>0;i--){
        d[i]=a[i];
        for(int j=i+1;j<=n;j++)if(a[i]<a[j])d[i]=max(d[i],d[j]+a[i]);
    }
    for(int i=2;i<=n;i++)d[1]=max(d[1],d[i]);
    cout<<d[1]<<endl;
    return 0;
}
递推版
 
           
          
 

猜你喜欢

转载自www.cnblogs.com/zach20040914/p/10425911.html