6.22 集训--DP复习一

总结

下午的突击练习完全不在状态

A、拦截导弹简单版

题目描述

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

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

输入格式

输入只有一行,为若干个正整数,一次为导弹的高度。

输出格式

第一行为最多能拦截的导弹数;
第二行为要拦截所有导弹最少要配备的系统数

样例

样例输入

389 207 155 300 299 170 158 65

样例输出

6
2

分析

这里要用到一个定理----最小链划分=最大反链长度
所以我们分别正着、倒着跑一遍LIS就可以了

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=1005;
int f1[maxn],f2[maxn],a[maxn],b[maxn];
    int cnt=1;
int main(){
    while(scanf("%d",&a[cnt])!=EOF) cnt++;
    cnt--;
    if(cnt==0){
        printf("0
0
");
        return 0;
    }
    for(int i=1;i<=cnt;i++) scanf("%d",&a[i]);
    for(int i=1;i<=cnt;i++) b[i]=a[cnt-i+1];
    int ans1=1,ans2=1;
    for(int i=1;i<=cnt;i++){
        for(int j=0;j<i;j++){
            if(a[i]>a[j]){
                f1[i]=max(f1[j]+1,f1[i]);
            }
            if(b[i]>=b[j]){
                f2[i]=max(f2[j]+1,f2[i]);
            }
            ans1=max(ans1,f1[i]);
            ans2=max(ans2,f2[i]);
        }
    }
    printf("%d
%d
",ans2,ans1);
    return 0;
}

B、挖地雷

题目描述

在一个地图上有N个地窖(N<=200),每个地窖中埋有一定数量的地雷。同时,给出地窖之间的连接路径,并规定路径都是单向的,也不存在可以从一个地窖出发经过若干地窖后又回到原来地窖的路径。

某人可以从任一处开始挖地雷,然后沿着指出的连接往下挖(仅能选择一条路径),当无连接时挖地雷工作结束。设计一个挖地雷的方案,使他能挖到最多的地雷。

输入格式

第一行一个整数n表示有n个地窖

第二行有n个整数表示每个地窖的地雷数

以下有若干行,每行有两个数x,y表示x可以到y,保证x小于y。

最后一行有两个0,表示输入结束

输出格式

第一行输出挖地雷的顺序。

第二行为最多挖出的地雷数

样例

样例输入

6
5 10 20 5 4 5
1 2
1 4
2 4
3 4
4 5
4 6
5 6
0 0

样例输出

3-4-5-6
34

分析

乍看上去要用SPFA以每一个点为起点跑一遍最长路
不过SPFA的时间效率很不稳定,会T,因此我们考虑别的方法
因为题目中的x一定是小于y的,所以我们可以像像线性DP那样定义f([j])为挖到编号为(j)的地窖的最大地雷数
如果(vis[j][i]==1),那么(f[i]=max(f[i],f[j]+a[i]))

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=205;
bool vis[maxn][maxn];
int a[maxn],f[maxn],jl[maxn];
int du[maxn];
int cnt=0;
int c[maxn];
int main(){
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
    }
    int aa,bb;
    while(scanf("%d%d",&aa,&bb)!=EOF && aa!=0){
        vis[aa][bb]=1;
        du[bb]++;
    }
    for(int i=1;i<=n;i++){
        if(du[i]==0) f[i]=a[i];
    }
    for(int i=1;i<=n;i++){
        for(int j=0;j<=i;j++){
            if(vis[j][i]){
                if(f[i]<f[j]+a[i]){
                    f[i]=f[j]+a[i];
                    jl[i]=j;
                }
            }
        }
    }
    int now=0,ans=0;
    for(int i=1;i<=n;i++){
        if(f[i]>ans){
            ans=f[i];
            now=i;
        }
    }
    c[++cnt]=now;
    while(jl[now]){
        c[++cnt]=jl[now];
        now=jl[now];
    }
    for(int i=cnt;i>=1;i--){
        if(i==cnt) printf("%d",c[i]);
        else printf("-%d",c[i]);
    }
    printf("
%d
",ans);
    return 0;
}

C. 飞翔

题目描述

鹰最骄傲的就是翱翔,但是鹰们互相都很嫉妒别的鹰比自己飞的快,更嫉妒其他的鹰比自己飞行的有技巧。于是,他们决定举办一场比赛,比赛的地方将在一个迷宫之中。

这些鹰的起始点被设在一个N*M矩阵的左下角map[1,1]的左下角。终点被设定在矩阵的右上角map[N,M]的右上角,有些map[i,j]是可以从中间穿越的。每一个方格的边长都是100米。如图所示:

没有障碍,也没有死路。这样设计主要是为了高速飞行的鹰们不要发现死路来不及调整而发生意外。

潘帕斯雄鹰冒着减RP的危险从比赛承办方戒备森严的基地中偷 来了施工的地图。但是问题也随之而来,他必须在比赛开始之前把地图的每一条路都搞清楚,从中找到一条到达终点最近的路。(哈哈,笨鸟不先飞也要拿冠军)但 是此鹰是前无古鹰,后无来鹰的吃菜长大的鹰--菜鸟。他自己没有办法得出最短的路径,于是紧急之下找到了学OI的你,希望找到你的帮助。

输入格式

首行为n,m(0<n,m<=1000000),第2行为k(0<k<=1000)表示有多少个特殊的边。以下k行为两个数,i,j表示map[i,j]是可以直接穿越的。

输出格式

仅一行,(1,1)-->(n,m)的最短路径的长度,四舍五入保留到整数即可

样例

样例输入

3 2
3
1 1
3 2
1 2

样例输出

383

分析

我们肯定要尽量使穿过对角线的边更多
所以我们把特殊边的横纵坐标存到结构体里,按照横坐标从小到大排序
然后用纵坐标跑一个LIS,得到的结果就是最多可以经过的特殊边的个数
那么最后的答案就是特殊边的个数( imes sqrt{200000} +)普通边的个数( imes 100)

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=1005;
int n,m,k;
struct asd{
    int x,y;
}b[maxn];
bool cmp(asd aa,asd bb){
    if(aa.x==bb.x) return aa.y<bb.y;
    return aa.x<bb.x;
}
int f[maxn];
int ans=0;
int main(){
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=k;i++){
        scanf("%d%d",&b[i].x,&b[i].y);
    }
    sort(b+1,b+1+k,cmp);
    for(int i=1;i<=k;i++){
        for(int j=0;j<i;j++){
            if(b[i].y>b[j].y && b[i].x!=b[j].x) f[i]=max(f[i],f[j]+1);
        }
        ans=max(ans,f[i]);
    }
    int tot=m+n-ans*2;
    double mans=(double)ans*sqrt(20000)+(double)tot*100.0;
    printf("%.0lf
",mans);
    return 0;
}

D. 麻烦的聚餐

题目描述

为了避免餐厅过分拥挤,FJ要求奶牛们分3批就餐。每天晚饭前,奶牛们都会在餐厅前排队入内,按FJ的设想,所有第3批就餐的奶牛排在队尾,队伍的前端由设定为第1批就餐的奶牛占据,中间的位置就归第2批就餐的奶牛了。

由于奶牛们不理解FJ的安排,晚饭前的排队成了一个大麻烦。

第i头奶牛有一张标明她用餐批次的卡片(Di(1 leq D_{i} leq 3))。虽然所有 (N (1 leq N leq 30000))头奶牛排成了很整齐的队伍,但谁都看得出来,卡片上的号码是完全杂乱无章的。

在若干次混乱的重新排队后,FJ找到了一种简单些的方法:

奶牛们不动,他沿着队伍从头到尾走一遍,把那些他认为排错队的奶牛卡片上的编号改 掉,最终得到一个他想要的每个组中的奶牛都站在一起的队列.
例如111222333或者333222111。
哦,你也发现了,FJ不反对一条前后颠倒的队列,那样他可以让所有奶牛向后转,然后按正常顺序进入餐厅。

你也晓得,FJ是个很懒的人。他想知道,如果他想达到目的,那么他最少得改多少头奶牛卡片上的编号。所有奶牛在FJ改卡片编号的时候,都不会挪位置

输入格式

第1行: 1个整数:N

第2..N+1行: 第i+1行是1个整数(D_{i}),为第i头奶牛的用餐批次

输出格式

第1行: 输出1个整数,为FJ最少要改几头奶牛卡片上的编号,才能让编号变成他设想中的样子

样例

样例输入

5
1
3
2
1
1

样例输出

1

数据范围与提示

【输入说明】

队列中共有5头奶牛,第1头以及最后2头奶牛被设定为第一批用餐,第2头奶牛的预设是第三批用餐,第3头则为第二批用餐。

【输出说明】

如果FJ想把当前队列改成一个不下降序列,他至少要改2头奶牛的编号,一种可行的方案是:

把队伍中2头编号不是1的奶牛的编号都改成1。
不过,如果FJ选择把第1头奶牛的编号改成3就能把奶牛们的队伍改造成一个合法的不上升序列了。

分析

这一道题N很大,要用到LIS的优化

代码

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=30010;
int a[maxn],b[maxn],c[maxn],d[maxn];
int lb(int x){
    return x&-x;
}
void ad(int x,int da){
    for(int i=x;i<maxn;i+=lb(i)){
        c[i]=max(c[i],da);
    }
}
int he(int x){
    int mmax=0;
    for(int i=x;i;i-=lb(i)){
        mmax=max(mmax,c[i]);
    }
    return mmax;
}
void ad1(int x,int da){
    for(int i=x;i<maxn;i+=lb(i)){
        d[i]=max(d[i],da);
    }
}
int he1(int x){
    int mmax=0;
    for(int i=x;i;i-=lb(i)){
        mmax=max(mmax,d[i]);
    }
    return mmax;
}
int n;
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        b[n-i+1]=a[i];
    }
    int ans=0;
    for(int i=1;i<=n;i++){
        c[a[i]]=he(a[i])+1;
        ans=max(ans,c[a[i]]);
        ad(a[i],c[a[i]]);
    }
    for(int i=1;i<=n;i++){
        d[b[i]]=he1(b[i])+1;
        ans=max(ans,d[b[i]]);
        ad1(b[i],d[b[i]]);
    }
    printf("%d
",n-ans);
    return 0;
}

E. 奶牛渡河

题目描述

Farmer John以及他的N(1 <= N <= 2,500)头奶牛打算过一条河,但他们所有的渡河工具,仅仅是一个木筏。

由于奶牛不会划船,在整个渡河过程中,FJ必须始终在木筏上。在这个基础上,木筏上的奶牛数目每增加1,FJ把木筏划到对岸就得花更多的时间。

当FJ一个人坐在木筏上,他把木筏划到对岸需要M(1 <= M <= 1000)分钟。
当木筏搭载的奶牛数目从i-1增加到i时,FJ得多花分钟才能把木筏划过河
也就是说,船上有1头奶牛时,FJ得花分钟渡河;船上有2头奶牛时,时间就变成分钟。后面 的依此类推。
那么,FJ最少要花多少时间,才能把所有奶牛带到对岸呢?当然,这个时间得包括FJ一个人把木筏从对岸划回来接下一批的奶牛的时间。

输入格式

第1行: 2个用空格隔开的整数:N 和 M

第2..N+1行: 第i+1为1个整数:

输出格式

第1行: 输出1个整数,为FJ把所有奶牛都载过河所需的最少时间

样例

样例输入

5 10
3
4
6
100
1

样例输出

50

数据范围与提示

【输入说明】

FJ带了5头奶牛出门。如果是单独把木筏划过河,FJ需要花10分钟,带上1头奶牛的话,是13分钟,2头奶牛是17分钟,3头是23分钟,4头是123分钟,将5头一次性载过去,花费的时间是124分钟。

【输出说明】

Farmer John第一次带3头奶牛过河(23分钟),然后一个人划回来(10分钟),最后带剩下的2头奶牛一起过河(17分钟),总共花费的时间是23+10+17 = 50分钟。

分析

我们设(sum[i])(FJ)(i)头奶牛运到对岸自己又回来的花费,设(f[i])为运输(i)头奶牛的最小花费
于是(f[i]=min(f[i],f[j]+sum[i-j]);)
最后得到的答案还要减去(m),因为(FJ)最后一次送奶牛过河不需要再回到出发点

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=2505;
typedef long long iint;
iint sum[maxn],f[maxn];
int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    sum[0]=m*2;
    f[0]=m*2;
    for(int i=1;i<=n;i++){
        iint aa;
        scanf("%lld",&aa);
        sum[i]=sum[i-1]+aa;
        f[i]=sum[i];
    }
    for(int i=1;i<=n;i++){
        for(int j=0;j<i;j++){
            f[i]=min(f[i],f[j]+sum[i-j]);
        }
    }
    printf("%lld
",f[n]-m);
    return 0;
}

F. 机器分配

题目描述

总公司拥有高效设备M台, 准备分给下属的N个分公司。各分公司若获得这些设备,可以为国家提供一定的盈利。问:如何分配这M台设备才能使国家得到的盈利最大?求出最大盈利值。其中M <= 15,N <= 10。分配原则:每个公司有权获得任意数目的设备,但总台数不超过设备数M。

输入格式

第1行有两个数,第一个数是分公司数N,第二个数是设备台数M。

接下来是一个N*M的矩阵,表明了第i个公司分配j台机器的盈利。

输出格式

第1行输出最大盈利值。

接下来N行,每行2个数,即分公司编号和该分公司获得设备台数。

样例

样例输入

?> 3 3
30 40 50
20 30 50
20 25 30

样例输出

70
1 1
2 1
3 1

分析

我们设(f[i][j])为前(i)个公司分配(j)台机器的最大利润
那么(f[i][j]=f[i-1][j-k]+a[i][k])
其中(0 leq k leq j)

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=2505;
int a[maxn][maxn],f[maxn][maxn],jl[maxn][maxn];
int cnt;
struct asd{
    int aa,bb;
}b[maxn];
bool cmp(asd cc,asd dd){
    return cc.aa<dd.aa;
}
int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            scanf("%d",&a[i][j]);
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            for(int k=0;k<=j;k++){
                if(f[i][j]<=f[i-1][j-k]+a[i][k]){
                    f[i][j]=f[i-1][j-k]+a[i][k];
                    jl[i][j]=k;
                }
            }
        }
    }
    printf("%d
",f[n][m]);
    while(n>0){
        b[++cnt].aa=n;
        b[cnt].bb=jl[n][m];
        m=m-jl[n][m];
        n--;
    }
     sort(b+1,b+1+cnt,cmp);
    for(int i=1;i<=cnt;i++){
        printf("%d %d
",b[i].aa,b[i].bb);
    }
    return 0;
}
原文地址:https://www.cnblogs.com/liuchanglc/p/13187895.html