动态规划测试test20170525

17 篇文章 0 订阅

前言

由于自己的一些年级组那边的事情影响了当时考试时的心情,然后就A了第一题就没什么心思去搞其他题目了,这是很不好的。

题目

火 车 票(ticket.cpp)

Timelimit :0.1s Name :Ticket

一个铁路线上有 n(2<=n<=10000)个火车站,每个火车站到该线路的首发火车站距离都是已知的。任意两站之间的票价如下表所示:

站之间的距离 – X票价
0XL1 C1
L1XL2 C2
L2XL3 C3

其中 L1L2L3C1C2C3 都是已知的正整数,且( 1L1<L2<L3109 , 1C1C2C3109 )。显然若两站之间的距离大于 L3 ,那么从一站到另一站至少要买两张
票。注意:每一张票在使用时只能从一站开始到另一站结束。
任务
对于给定的线路,求出从该线路上的站 A 到站 B 的最少票价。
输入
输入文件的第一行为 6 个整数, L1,L2,L3,C1,C2,C3(1L1L2L3109,1C1C2C3109) ,这些整数由空格隔开.第二行为火车站的数量 N ( 2N10000 ).
第三行为两个不同的整数 A、B,由空格隔开。接下来的 N-1 行包含从第一站到其他站之间
的距离.这些距离按照增长的顺序被设置为不同的正整数。相邻两站之间的距离不超过 L3 .
两个给定火车站之间行程花费的最小值不超过 109 ,而且任意两站之间距离不超过 109
输出
输出文件中只有一个数字,表示从 A 到 B 要花费的最小值.
样例输入
3 6 8 20 30 40
7
2 6
3
7
8
13
15
23
样例输出
70

题解:
这题目原来一本通的习题上有,不过不要优化 O(n2) 可以过,这里要单调优化即可。
代码:

#include <cstdio>
#include <cstring>
#include <algorithm>

#define Max(a,b) ((a)>(b)?(a):(b))
#define Min(a,b) ((a)<(b)?(a):(b))
typedef long long LL;

const int maxn = 10000+10;
const int INF = 2e9;

inline int read(int &in) {
    in=0;int f=1;char ch=getchar();
    for(;ch<'0'||ch>'9';ch=getchar())
        if(ch=='-') f=-1;
    for(;ch>='0'&&ch<='9';ch=getchar())
        in=in*10+ch-'0';
    return in*f;
}

int n,sta,lst;
int l[5],c[5];
int k[5],p[5];
int f[maxn];
int dist[maxn],dis;

namespace my_self {
    void init() {
        for(int i=1;i<maxn;i++)
            f[i]=0;
        read(l[1]);read(l[2]);read(l[3]);read(c[1]);read(c[2]);read(c[3]);
        read(n);read(sta);read(lst);
        dist[1]=0;
        for(int i=2;i<=n;i++)
            read(dist[i]);
    }
    void work() {
        p[1]=l[1];p[2]=l[2];p[3]=l[3];k[1]=k[2]=k[3]=sta;
        for(int i=sta+1;i<=lst;i++) {
            dis=dist[i]-dist[i-1];f[i]=INF;
            for(int j=1;j<=3;j++) {
                if(dis<=p[j]) {
                    p[j]-=dis;
                    if(f[k[j]]+c[j]<f[i]) f[i]=f[k[j]]+c[j];
                }
                else {
                    while(k[j]<i) {
                        k[j]++;if(dist[i]-dist[k[j]]<=l[j]) break;
                    }
                    if((k[j]<=i) && (dist[i]-dist[k[j]]<=l[j])) {
                        p[j]=l[j]-(dist[i]-dist[k[j]]);
                        f[i]=Min(f[i],c[j]+f[k[j]]);
                    }
                    else p[j]=l[j];
                }
            }
        }
        printf("%d\n",f[lst]);
    }
}

int main() {
    freopen("ticket.in","r",stdin);
    freopen("ticket.out","w",stdout);
    my_self::init();
    my_self::work();
    return 0;
}

艺术馆的火灾(fire.cpp)

背景描述
这幢古老的建筑是一个艺术馆,它珍藏着上百件绘画、雕塑以及其他艺术品,就连建筑本身也是一件艺术。但是,岁月并不在乎它的精致与美丽,时光在渐渐剥夺着这幢木屋的生命。终于,在一个月色昏暗的夜晚,它着火了。艺术馆是一幢两层的小楼,每一层有 N 个房间,每个房间中收藏的艺术品的价值都可以用一个正整数来表示。下面是一个 N=4 的例子。
  屋顶
40 50 30 60
30 30 40 20
在这个例子中,二层楼的第四个房间中艺术品的价值最大,为 60。而一层楼的第四个房间中艺术品的价值仅为 20。在消防队员火速赶到的时候,火势已经蔓延了整个建筑,消防队员们观察每个房间中的火势,将它们分别用一个正整数来表示。在上面的例子中,各房间中的火势可能如下。
  屋顶
50 40 50 70
40 50 20 30
你可以看到,二层楼的第四个房间中火势最强,为 70。而一层楼的第三个房间中火势较弱,为 20。
由于火情紧急,消防队员们准备使用一种新型的灭火器。这种灭火器只能发射 K 次,每次发射将覆盖一个矩形的区域(矩形的高度可以是 1 也可以是 2)。它的威力巨大,所到之处不但火焰会被扑灭,就连同在一处的艺术品也难以幸免。因此,如何善用这种灭火器成了最大的问题。一个例子:如果灭火器的一次发射覆盖了下图阴影所示的 2×2 矩形区域,那么这四个房间的火势和艺术品价值都将成为 0。
任务说明
给出艺术馆每层的房间数 N 和灭火器的发射次数 K,以及每个房间中艺术品的价值和火势,你需要决定灭火器每次应该怎样发射(也可以不发射),才能将这次火灾的损失降到最低限度。这个损失用你所摧毁的艺术品的总价值,加上剩余的火势总值(这些火焰将需要消防队员们亲身去扑灭)来衡量。
输入数据
输入文件的第一行有两个整数 N(1 <= N <= 100)、K(1<= K <= 10),分别表示艺术馆中每层的房间数和灭火器的发射次数。
接下来的两行每行有 N 个整数,其中第 4-i 行的第 j 个整数 Vi,j表示的是第 i层第 j 个房间中艺术品的价值(1 <= i <= 2,1 <= j <= N,1 <= Vi,j <= 10000)。
再接下来的两行每行也有 N 个整数,其中第 6-i 行的第 j 个整数 Fi,j 表示的是第 i 层第 j 个房间中的火势(1 <= i <=2,1 <= j <= N,1 <= Fi,j <= 10000)。
输出数据
你的输出文件应该有 K 行,每行有四个整数 L1、R1、L2、R2,表示你的灭火器的一次行动。如果灭火器这次不发射,那么这四个整数都为 0;否则这次发射所覆盖的矩形区域的左下角是第 L1 层的第 R1 个房间,右上角是第 L2 层的第R2 个房间。
注意:你的每次发射所覆盖的矩形区域必须位于小楼之内,并且矩形的面积至少为 0。即 1 <= L1 ,L2 <= 2,1 <= R1 , R2 <= N。
输入输出样例
fire.in
4 2
40 50 30 60
30 30 40 20
50 40 50 70
40 50 20 30
fire.out
1 1 1 2
2 3 2 4
样例解释
摧毁艺术品:30+60+30+30=150
剩余火势:50+40+20+30=140
最小损失:150+140=290
评分标准
每个测试点都有一定的时间限制。你的程序只有在规定的时限内输出解,并且按照你的方案来发射灭火器可以将损失降到最小,你才会得到相应测试点的分值;否则这个测试点不得分。
注意:本题的解不一定是唯一的。即对于一个测试数据,可能有多种发射灭火器的方案
都可以使损失达到最小。在这种情况下,你只要输出最优方案中的任意一种即可。

题解:
首先将问题转化成模型:在一个2×N的矩阵中,找出K个子矩阵。我们将这些子矩阵所包含的元素集合用S来表示的话,那么问题的目标就是使 eSVe+eSFe 这个值最小。
因为 eSFe+eSFe=Sum(F) (Sum(F)表示F矩阵中的元素总和),所以上面的式子可以化为 eSVe+(Sum(F)eSFe) ,而Sum(F)又是个常数,所以实际上问题的目标就是使 eS(VeFe) 最小。
下面是规划方程:
f(s,i,j)=minf(s,i1,j)f(s,i,j1)f(s1,t1,j)+sum(1,t,1,i)(1ti)f(s1,i,t1)+sum(2,t,2,j)(1tj)f(s1,t1,t1)+sum(1,t,2,min(i,j))(1tmin(i,j))
边界条件
f(0,i,j)=0(0i,jN) f(s,0,0)=0(0sk)

然后输出就得在状态转移时记录即可。

代码:

    #include <iostream>
    #include <fstream>
    #include <cassert>
    using namespace std;
    ifstream fin("Input.txt");
    ofstream fout("Output.txt");
    int room_n,shoot_k,v[3][101],f[3][101];

    /*dp部分*/
    int dp[101][5][11];//[i][j][k]:前i列已经最少有k个完整矩形、第i列的状态为j之后还能获得的最小损失
    bool checked[101][5][11];
    int memo(int i,int j, int k);
    int loss(int i, int j); //memo调用的子过程,返回第i行决策为j时的净损失

    /*最优解的构造部分*/
    int build[101][5][11];  //记录(i,j,k)状态下要得到最优解,i+1列的状态
    bool room[3][101];
    void paint(int i, int j, int k);//在room中标出状态(i,j,k)后的灭火器使用情况

    /*最优解的输出部分*/
    int counter;//记录发射次数
    void output(int i);  //输出矩形

    int main()
    {
       /*输入部分*/
       fin >> room_n >> shoot_k;
       for(int i=2;i>=1;i--)
          for(int j=1;j<=room_n;j++)
             fin >> v[i][j];
       for(int i=2;i>=1;i--)
          for(int j=1;j<=room_n;j++)
             fin >> f[i][j];
       /*计算部分*/
       unsigned min_loss=memo(0,0,0);
       paint(0,0,0);

       /*输出部分*/
       for(int i=1;i<=room_n;i++) output(i);
       for(int j=shoot_k-counter;j>=1;j--)
          fout << "0 0 0 0\n";
       return 0;
    }

    //
    int memo(int i,int j, int k)
    {
       if(checked[i][j][k]) return dp[i][j][k];
       if(i==room_n)  return 0;
       dp[i][j][k]=INT_MAX;
       for(int jj=0,kk,ans;jj<=3;jj++)  //枚举第i+1行状态
       {
          if(jj==3&&(j==4||j==1||j==2))  jj++; //上下都用时的状态修正
          kk=k+!(jj==0||jj==j||j==4);
          if(kk<=shoot_k)
             ans=memo(i+1,jj,kk)+loss(i+1,jj);
          else ans=INT_MAX;
          if(dp[i][j][k]>ans)
          {
             dp[i][j][k]=ans;
             build[i][j][k]=jj;
          }
       }
       assert(dp[i][j][k]<INT_MAX);
       checked[i][j][k]=true;
       return dp[i][j][k];
    }
    int loss(int i, int j)
    {
    /*状态j的定义:
       0:上层下层都不使用灭火器;
       1:上使用;2:下使用;3,4:上下都用
       其中3表示与此列相连的使用灭火器的房间最少组成1个完整矩形
       4表示与此列相连的使用灭火器的房间已经最少组成2个完整矩形*/
       switch(j)
       {
          case 0:
             return (f[1][i]+f[2][i]);  //上下都不用
          case 1:
             return(v[2][i]+f[1][i]);   //上用
          case 2:
             return(v[1][i]+f[2][i]);   //下用
          case 3:  //上下都用
          case 4:
             return(v[1][i]+v[2][i]);
       }
       assert(0);
    }
    //
    void paint(int i, int j, int k)
    {
       if(i==room_n)  return;
       assert(checked[i][j][k]);
       int jj=build[i][j][k];
       int kk=k+!(jj==0||jj==j||j==4);
       switch(jj)
       {
          case 0:
             break;   //上下都不用
          case 1:
             room[2][i+1]=1; break;  //上用
          case 2:
             room[1][i+1]=1; break;  //下用
          case 3:
          case 4:
             room[1][i+1]=room[2][i+1]=true; break; //上下都用
          default:
             assert(0);
       }
       paint(i+1,jj,kk);
    }
    //
    void output(int i)
    {
       counter++;
       int ii;
       if(room[2][i]&&!room[1][i]) //上用
       {
          fout << 2 << ' ' << i << ' ';
          for(ii=i;ii<=room_n&&room[2][ii];ii++)  room[2][ii]=0;
          fout << 2 << ' ' << ii-1 << endl;
       }
       else if(room[1][i]&&!room[2][i])  //下用
       {
          fout << 1 << ' ' << i << ' ';
          for(ii=i;ii<=room_n&&room[1][ii];ii++)  room[1][ii]=0;
          fout << 1 << ' ' << ii-1 << endl;
       }
       else if(room[1][i]&&room[2][i])   //上下都用
       {
          fout << 1 << ' ' << i << ' ';
          for(ii=i;ii<=room_n&&room[1][ii]&&room[2][ii];ii++)  room[1][ii]=room[2][ii]=0;
          fout << 2 << ' ' << ii-1 << endl;
       }
       else counter--;   //上下都不用
    }
    //

序列压缩(substract.cpp)

给出一个 N 个正整数的序列 A=[A1,A2,……,AN],我们可以对其进行压缩操作.所谓压缩
操作是指:将两个相邻的元素 AI和 AI+1的差(AI-AI+1)去替代第 I 个元素,同时删去第 I+1 个元素,严格地可以定义如下:
CON(A,I)=[A1,,A2,….,AI-1,AI-AI+1,AI+2,…….AN]
经过一次序列压缩之后,我们可以得到一个 N-1 个元素的序列,如此进行下去,我们可以仅得到单一的一个整数。如下例所示:
con([12 ,10, 4, 3, 5],2)=[12 ,6,3,5]
con([12,6,3,5],3)=[12 ,6,-2]
con([12,6,-2],2)=[12,8]
con([12,8],1)=[4]
现给定一个正整数序列和一个目标整数 T,求解并输出压缩顺序。
输入文件:
N T (1〈=N〈=100,-10000〈=T〈=10000)
A1 A2 AN
输出文件:
压缩顺序;
输入输出示例:
SUBSTRACT.IN
5 4
12 10 4 3 5
SUBSTRACT.OUT
2 3 2 1

题解:
我们可以把题目转化为在一列数中添加+ - 。
我们记所有加上 -号的数的和为S,所有数的和为sum,容易得到S=(sum-T)/2;
f[i][j] 记前i个数中若干个数的和能否得到j,并记下递推路径,最后根据路径输出 f[N][S] 即可。

代码:

#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <iostream>
using namespace std;

#define Max(a,b) ((a)>(b)?(a):(b))
#define Min(a,b) ((a)<(b)?(a):(b))
typedef long long LL;

const int maxn = 10010;

int num[maxn],pre[maxn],vis[maxn];
int sum,n,t,m;
bool ps[maxn];
string st;
char str[maxn];

char del (char *s,int p) {
    char ch;
    if(p>=strlen(s) or p<0) return 0;
    ch=s[p];
    for(int i=p;s[i];i++) s[i]=s[i+1];
    return ch;
}

namespace my_self {
    void init() {
        scanf("%d%d",&n,&t);
        for(int i=1;i<=n;i++) {
            scanf("%d",&num[i]);
            sum+=num[i];
        }
        m=(sum-t)/2;
    }

    void work() {
        vis[num[2]]=true;pre[num[2]]=2;
        for(int i=3;i<=n;i++) {
            if(vis[m]) break;
            for(int j=m;j>=num[i];j--) {
                if(!vis[j]) {
                    if(vis[j-num[i]]) {
                        vis[j]=true;pre[j]=i;
                    }
                }
            }
        }
    }

    void print() {
        for(int i=1;i<maxn;i++)
            ps[i]=true;
        int s=m;
        while(s>0) {
            ps[pre[s]]=false;s-=num[pre[s]];
        }
        for(int i=2;i<=n;i++) {
            if(ps[i]) st=st+'+';
            else st=st+'-';
        }
        for(int i=0;i<st.length();i++)
            str[i]=st[i];
        char *pch=strchr(str,'+');
        while(pch!=NULL) {
            printf("%d ",pch-str+1);
            del(str,pch-str);
            pch=strchr(str,'+');
        }
        for(int i=0;i<strlen(str);i++)
            putchar('1'),putchar(' ');
        putchar(10);
    }

}

int main() {
    freopen("substract.in","r",stdin);
    freopen("substract.out","w",stdout);
    my_self::init();
    my_self::work();
    my_self::print();
    //std::cout<<st<<std::endl;
    return 0;
}

/*
5 4
12 10 4 3 5
*/
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值