【动态规划32讲】第四节 动态规划入门[不懂]

例题3                         来源: USACO 4-3-1

【问题描述】

    “逢低吸纳”是炒股的一条成功秘诀。如果你想成为一个成功的投资者,就要遵守这条秘诀:

"逢低吸纳,越低越买"

这句话的意思是:每次你购买股票时的股价一定要比你上次购买时的股价低.按照这个规则购买股票的次数越多越好,看看你最多能按这个规则买几次。

给定连续的N天中每天的股价。你可以在任何一天购买一次股票,但是购买时的股价一定要比你上次购买时的股价低。写一个程序,求出最多能买几次股票。

以下面这个表为例, 某几天的股价是:

天数 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
【输入文件】buylow.in

第1行: N (1 <= N <= 5000), 表示能买股票的天数。

第2行以下: N个正整数 (可能分多行) ,第i个正整数表示第i天的股价. 这些正整数大小不会超过longint(pascal)/long(c++).

【输出文件】buylow.out

只有一行,输出两个整数:

能够买进股票的天数长度   达到这个值的股票购买方案数量

在计算解的数量的时候,如果两个解所组成的字符串相同,那么这样的两个解被认为是相同的(只能算做一个解)。因此,两个不同的购买方案可能产生同一个字符串,这样只能计算一次。

【输入样例】

12
68 69 54 64 68 64 70 67 78 62 98 87
【输出样例】

4 2

【问题分析】

从题目描述就可以看出这是最长下降子序列问题,于是求解第一问不是难事,以天数为阶段,设计状态opt[i] 表示前i天中要买第i天的股票所能得到的最大购买次数。

状态转移方程:

   opt[i]=max(opt[j])+1  (a[i]>=a[j],0=<j<i)   {a[i]存第i天股价}

最大的opt[i]就是最终的解。

第二问呢,稍麻烦一点。

从问题的边界出发考虑问题,当第一问求得一个最优解opt[i]=X时,

 在1到N中如果还有另外一个opt[j]=x那么选取J的这个方案肯定是成立的。

 是不是统计所有的opt[j]=x 的个数就是解了呢?  

显然没那么简单,因为选取J这天的股票构成的方案不见得=1,看下面一个例子:

5 6  4 3 1 2

方案一:5431

方案二:5432

方案三:6431

方案四:6432

x=4

也就是所虽然opt[5]=X 和opt[6]=X但个数只有两个,而实际上应该有四个方案,但在仔细观察发现:

构成opt[5]=x 的这个方案中 opt[j]=x-1的方案数有两个(5 4 3 和6 4 3)

opt[j]=x-2的有一个,opt[j]=x-3 的有一个……

显然这是满足递归定义的设计函数F(i)表示前I张中用到第i张股票的方案数。

 递推式:

          1  (i=0)

 F(i)=

          Sum(F(j))  (0<=j<=n,a[j]>a[i],opt[j]=opt[i]-1)  {sum 代表求和}

答案=sum(F(j))    {0<j<=n,opt[j]=x}

复杂度:

求解第一问时间复杂度是O(N2),求解第二问如果用递推或递归+记忆化时间复杂度仍为O(N2)但要是用赤裸裸的递归那就复杂多了……,因为那样造成了好多不必要的计算。

你认为这样做就解决了这道题目了么?还没有,还要注意一些东西:

(1)如果有两个方案中出现序列一样,视为一个方案,要需要加一个数组next用next[i] 记录和第i个数情况一样(即:opt[i]=opt[j] 且 a[i]=a[j])可看做一个方案的最近的位置。

     递推时j只要走到next[i]即可。

(2)为了方便操作可以将a[n+1]赋值为-maxlongint这样可以认为第n+1个一定可以买,答案就是sum(F(n+1))。

(3)看数据规模显然要用高精度。

 注:USACO上最后一个点错了。我在程序中做了处理。

【源代码】#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
typedef struct BIGNUM *bignum_t;
struct BIGNUM
{
  int val;
  bignum_t next;
};
int num[5000]; //存储给定的数据
int len[5000];  //保存最大下降序列值
int nlen;   //数据数量
bignum_t cnt[5000];
bignum_t get_big(void)
{
  static bignum_t block;
  static int size = 0;
  if (size == 0)
  {
   block = (bignum_t)malloc(sizeof(*block)*128);
   size = 128;
  }
  size--;
  return block++;
}
  /*初始化高精度数*/
void init_big(bignum_t *num, int val)
{
  *num = get_big();
  /* initialize */
  (*num)->val = val;
  (*num)->next = NULL;
}
void add(bignum_t a, bignum_t b)
{
  int c; /* carry */
  c = 0;
  while (b || c)
  {
   a->val += c;
   if (b) a->val += b->val;
   /* if a->val is too large, we need to carry */
   c = (a->val / 1000000); //4字节int最大值为2147483647
   a->val = (a->val % 1000000);
   if (b) b = b->next;
   if (!a->next && (b || c))
 { /* allocate if we need to */
    a->next = get_big();
    a = a->next;
    a->val = 0;
    a->next = NULL;
   }else
   a = a->next;
  }
}
void out_num(FILE *f, bignum_t v)
{
  if (v->next)
  {
   out_num(f, v->next);
   fprintf (f, "%06i", v->val);
  }
  else
   fprintf (f, "%i", v->val);
}
int main(int argc, char **argv)
{
  FILE *fout, *fin;
  int lv, lv2;
  int c;
  int max;
  int l;
  bignum_t ans;
  if ((fin = fopen("in.txt", "r")) == NULL)
  {
   perror("fopen fin"); //perror(str)打印str和一个相应的错误消息
   exit(1);
  }
  if ((fout = fopen("out.txt", "w")) == NULL)
  {
   perror ("fopen fout");
   exit(1);
  }
  fscanf (fin, "%d", &nlen);
  for (lv = 0; lv < nlen; lv++)
   fscanf (fin, "%d", &num[lv]);
  /* 用DP计算最大长度*/
  for (lv = 0; lv < nlen; lv++)
  {
   max = 1;
   for (lv2 = lv-1; lv2 >= 0; lv2--)
    if (num[lv2] > num[lv] && len[lv2]+1 > max)
      max = len[lv2]+1;
   len[lv] = max;
  }
  for (lv = 0; lv < nlen; lv++)
  {
   if (len[lv] == 1)
   init_big(&cnt[lv], 1);
   else
   {
    init_big(&cnt[lv], 0);
    l = -1;
    max = len[lv]-1;
    for (lv2 = lv-1; lv2 >= 0; lv2--)
    if (len[lv2] == max && num[lv2] > num[lv] && num[lv2] != l)
     add(cnt[lv], cnt[lv2]);
    l = num[lv2];
   }
  }
  /* 找最长串*/
  max = 0;
  for (lv = 0; lv < nlen; lv++)
  if (len[lv] > max) max = len[lv];
  init_big(&ans, 0);
  l = -1;
  for (lv = nlen-1; lv >= 0; lv--)
  if (len[lv] == max && num[lv] != l)
  {
  add(ans, cnt[lv]);
  l = num[lv];
  }
  /* output answer */
  fprintf (fout, "%i ", max);
  out_num(fout, ans);
  fprintf (fout, "\n");
  return 0;
}

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值