线性动态规划 (共六题)

这六个题的整体是有点难度的。

P1020 导弹拦截

URL: https://www.luogu.org/problem/show?pid=1020

题目描述

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

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

输入输出格式

输入格式:
一行,若干个正整数最多100个。

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

输入输出样例

输入样例#1:
389 207 155 300 299 170 158 65
输出样例#1:
6
2

#include<stdio.h>

const int MAXN=105;
const int MAXD=3e4+5;
int dp[MAXD],que[MAXN],d,u,top;

int main()
{
    while(scanf("%d",&d)!=EOF){
        int p=0;
        while(p<u){
            if(que[p]>d) break;
            ++p;
        }
        if(p<u) que[p]=d;
        else que[u++]=d;

        top+=d;
        int max=0;
        for(int i=top;i>=d;--i)
            if(dp[i]>max) max=dp[i];
        if(max+1>dp[d]) dp[d]=max+1;
    }
    int ans=0;
    for(int i=0;i<=top;++i) if(dp[i]>ans) ans=dp[i];
    printf("%d\n%d\n",ans,u);
    return 0;
}

P1091 合唱队形

URL: https://www.luogu.org/problem/show?pid=1091
题目描述

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

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

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

输入输出格式

输入格式:
输入文件chorus.in的第一行是一个整数N(2<=N<=100),表示同学的总数。第一行有n个整数,用空格分隔,第i个整数Ti(130<=Ti<=230)是第i位同学的身高(厘米)。

输出格式:
输出文件chorus.out包括一行,这一行只包含一个整数,就是最少需要几位同学出列。

输入输出样例

输入样例#1:
8
186 186 150 200 160 130 197 220
输出样例#1:
4
说明

对于50%的数据,保证有n<=20;

对于全部的数据,保证有n<=100。

题解

有一个坑啊,单调的队伍也是满足条件的

#include<stdio.h>

const int MAXN=1e2+5;
int up[MAXN],un[MAXN],num[MAXN],n;

int main()
{
    scanf("%d",&n);
    for(int i=0;i<n;++i){
        scanf("%d",&num[i]);
        num[i]-=130;
    }
    for(int i=0;i<n;++i){
        int max=0;
        for(int k=0;k<i;++k)
            if(num[k]<num[i]&&up[k]>max)
                max=up[k];
        up[i]=max+1;
    }
    for(int i=n-1;i>=0;--i){
        int max=0;
        for(int k=n-1;k>i;--k)
            if(num[i]>num[k]&&un[k]>max)
                max=un[k];
        un[i]=max+1;
    }
    int ans=0;
    for(int i=0;i<n;++i)
        if(up[i]+un[i]-1>ans)
            ans=up[i]+un[i]-1;
    printf("%d\n",n-ans);
    return 0;
}

P1280 尼克的任务

URL: https://www.luogu.org/problem/show?pid=1280
题目描述

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

尼克的一个工作日为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

题解

这是一道不好想的题,人们都习惯正向思维,而这个题逆向想就非常简单了

#include<stdio.h>
#include<vector>
using namespace std;

const int MAXN=1e4+5;
int dp[MAXN],T,n,s,w;
vector<int> vec[MAXN];

int main()
{
    scanf("%d%d",&T,&n);++T;
    for(int i=0;i<n;++i){
        scanf("%d%d",&s,&w);
        vec[s].push_back(w);
    }
    for(int i=T;i>0;--i){
        if(vec[i].size()==0) dp[i]=dp[i+1]+1;
        for(int j=0;j<vec[i].size();++j){
            int t=vec[i][j];
            dp[i]=max(dp[i],dp[i+t]);
        }
    }
    printf("%d\n",dp[1]-1);
    return 0;
}

P1880 石子合并

URL: https://www.luogu.org/problem/show?pid=1880
题目描述

在一个园形操场的四周摆放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

题解

区间dp

#include<stdio.h>
#include<string.h>

const int MAXN=1e2+5;
int dp[MAXN][MAXN],dd[MAXN][MAXN],sum[MAXN],n;

int max(int a,int b){return a>b?a:b;}
int min(int a,int b){return a<b?a:b;}
int abs(int a){return a<0?-a:a;}

int main()
{
    scanf("%d",&n);
    memset(dd,0x3f,sizeof(dd));
    for(int i=0;i<n;++i){
        int t;
        scanf("%d",&t);
        sum[i]=(i>0?sum[i-1]:0)+t;
        dd[i][i]=0;
    }
    int low=1<<30,top=0;
    for(int d=1;d<n;++d){
        for(int L=0;L<n;++L){
            int R=(L+d)%n;
            for(int i=L;i<L+d;++i){
                int r=i%n;
                int l=(i+1)%n;
                dp[L][R]=max(dp[L][R],dp[L][r]+dp[l][R]
                                +(R>L?
                                (sum[R]-(L-1>=0?sum[L-1]:0)):
                                (sum[n-1]-sum[L-1]+sum[R])));
                dd[L][R]=min(dd[L][R],dd[L][r]+dd[l][R]
                                +(R>L?
                                (sum[R]-(L-1>=0?sum[L-1]:0)):
                                (sum[n-1]-sum[L-1]+sum[R])));
            }
        //  printf("[%d %d] ---> %d\n",L,R,dp[L][R]);
            if(d==n-1){
                top=max(top,dp[L][R]);
                low=min(low,dd[L][R]);
            }
        }
    }
    printf("%d\n%d\n",low,top);
    return 0;
}

P1108 低价购买

URL: https://www.luogu.org/problem/show?pid=1108
题目描述

“低价购买”这条建议是在奶牛股票市场取得成功的一半规则。要想被认为是伟大的投资者,你必须遵循以下的问题建议:“低价购买;再低价购买”。每次你购买一支股票,你必须用低于你上次购买它的价格购买它。买的次数越多越好!你的目标是在遵循以上建议的前提下,求你最多能购买股票的次数。你将被给出一段时间内一支股票每天的出售价(2^16范围内的正整数),你可以选择在哪些天购买这支股票。每次购买都必须遵循“低价购买;再低价购买”的原则。写一个程序计算最大购买次数。

这里是某支股票的价格清单:

日期 1 2 3 4 5 6 7 8 9 10 11 12

价格 68 69 54 64 68 64 70 67 78 62 98 87

最优秀的投资者可以购买最多4次股票,可行方案中的一种是:

日期 2 5 6 10

价格 69 68 64 62

输入输出格式

输入格式:
第1行: N (1 <= N <= 5000),股票发行天数

第2行: N个数,是每天的股票价格。

输出格式:
输出文件仅一行包含两个数:最大购买次数和拥有最大购买次数的方案数(<=2^31)当二种方案“看起来一样”时(就是说它们构成的价格队列一样的时候),这2种方案被认为是相同的。

输入输出样例

输入样例#1:

12
68 69 54 64 68 64 70 67 78 62 98 87
输出样例#1:

4 2

题解

这是一道很不好想的,想明白注释那儿为什么可以一个条件判断同类,这个题也就差不多了

#include<stdio.h>

const int MAXN=5e3+5;
int dp[MAXN],arr[MAXN],num[MAXN],n,best;

int main(){
    scanf("%d",&n);
    for(int i=0;i<n;++i){
        scanf("%d",&arr[i]);
        int up=0;
        for(int j=0;j<i;++j) if(arr[j]>arr[i]&&dp[j]>up) up=dp[j];
        dp[i]=up+1;
        if(up==0) num[i]=1;
        for(int j=0;j<i;++j){
            if(dp[i]==dp[j]&&arr[i]==arr[j]) num[j]=0; //等价 同类合并
            else if(dp[i]-1==dp[j]&&arr[i]<arr[j]) num[i]+=num[j];
        }
        if(dp[i]>best) best=dp[i];
    }
    int nums=0;
    for(int i=0;i<n;++i) if(dp[i]==best) nums+=num[i];
    printf("%d %d\n",best,nums);
    return 0;
}

P1282 多米诺骨牌

URL: https://www.luogu.org/problem/show?pid=1282#sub
题目描述

多米诺骨牌有上下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

题解

82分的代码,方法的可行性貌似是有问题的,没继续改了,想到了另一个方法。

#include<stdio.h>
#include<string.h>
#include<queue>
#include<bitset>
using namespace std;

const int MAXN=1e4+10;
const int ADD=5000;
const int INF=0x3f3f3f3f;
int dp[MAXN],arr[1005],n,sum;
bitset<1005> set[MAXN];

int main()
{
    scanf("%d",&n);
    for(int i=0;i<n;++i){
        int up,don;
        scanf("%d%d",&up,&don);
        arr[i]=up-don;
        sum+=arr[i];
    }
    memset(dp,0x3f,sizeof(dp));
    dp[sum+ADD]=0;
    queue<int> que;
    que.push(sum);
    while(!que.empty()){
        int v=que.front();que.pop();
        for(int i=0;i<n;++i){
            if(set[v+ADD][i]) continue;
            int to=v-2*arr[i];
            if(dp[v+ADD]+1<dp[to+ADD]){
                dp[to+ADD]=dp[v+ADD]+1;
                set[to+ADD]=set[v+ADD];
                set[to+ADD].reset(i);
                que.push(to);
            }
        }
    }
    int tp=0;
    while(dp[ADD+tp]==INF&&dp[ADD-tp]==INF) tp++;
    printf("%d\n",dp[ADD+tp]<dp[ADD-tp]?dp[ADD+tp]:dp[ADD-tp]);
    return 0;
}

AC代码

把问题抽象成多重背包问题
然后优化了一下,复杂度还是比较低的 O(MAXN * sigma(logn)),跑完 28ms

#include<stdio.h>
#include<string.h>

const int MAXN=2e4+10;
const int ADD=1e4;
const int INF=0x3f3f3f3f;
int dp[MAXN],arr[11],sum,n;

int min(int a,int b){return a<b?a:b;}

int main()
{
    scanf("%d",&n);
    for(int i=0;i<n;++i){
        int up,don;
        scanf("%d%d",&up,&don);
        sum+=up-don;
        ++arr[up-don+5];
    }
    memset(dp,0x3f,sizeof(dp));
    dp[sum+ADD]=0;
    for(int i=-5;i<6;++i){
        int num=arr[i+5];
        for(int j=1;i*num;j<<=1){
            int X=j<num?j:num;
            num-=X;
            int x=-2*X*i;
            if(i>0) for(int k=-x;k<MAXN;++k){
                if(dp[k]+X<dp[k+x]) dp[k+x]=dp[k]+X;
            }else for(int k=MAXN-1-x;k>=0;--k){
                if(dp[k]+X<dp[k+x]) dp[k+x]=dp[k]+X;
            }
        }
    }
    int tp=0;
    while(dp[ADD+tp]==INF&&dp[ADD-tp]==INF) ++tp;
    printf("%d\n",min(dp[ADD+tp],dp[ADD-tp]));
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值