【★】动态规划例题

ref:http://topcoder.5d6d.com/thread-75-1-1.html



  1. USACO
  2. 2.2 Subset Sums
  3. 题目如下:
  4. 对于从1到N的连续整集合合,能划分成两个子集合,且保证每个集合的数字和是相等的。
  5. 举个例子,如果N=3,对于{1,2,3}能划分成两个子集合,他们每个的所有数字和是相等的:
  6. and {1,2}
  7. 这是唯一一种分发(交换集合位置被认为是同一种划分方案,因此不会增加划分方案总数)
  8. 如果N=7,有四种方法能划分集合{1,2,3,4,5,6,7},每一种分发的子集合各数字和是相等的:
  9. {1,6,7} and {2,3,4,5} {注 1+6+7=2+3+4+5}
  10. {2,5,7} and {1,3,4,6}
  11. {3,4,7} and {1,2,5,6}
  12. {1,2,4,7} and {3,5,6}
  13. 给出N,你的程序应该输出划分方案总数,如果不存在这样的划分方案,则输出0。程序不能预存结果直接输出。
  14. PROGRAM NAME: subset
  15. INPUT FORMAT

  16. 输入文件只有一行,且只有一个整数N
  17. SAMPLE INPUT (file subset.in)
  18. 7
  19. OUTPUT FORMAT
  20. 输出划分方案总数,如果不存在则输出0。
  21. SAMPLE OUTPUT (file subset.out)
  22. 4

  1. 参考程序如下:

  2. #include <fstream>
  3. using namespace std;
  4. const unsigned int MAX_SUM = 1024;
  5. int n;
  6. unsigned long long int dyn[MAX_SUM];
  7. ifstream fin ("subset.in");
  8. ofstream fout ("subset.out");
  9. int main() {
  10. fin >> n;
  11. fin.close();
  12. int s = n(n+1);
  13. if (s % 4) {
  14.        fout << 0 << endl;
  15.        fout.close ();
  16.        return ;
  17. }
  18. s /= 4;
  19. int i, j;
  20. dyn [0] = 1;
  21. for (i = 1; i <= n; i++)
  22.        for (j = s; j >= i; j–)
  23.          dyn[j] += dyn[j-i];      //前i个数中能拼凑出和为j的种数:dyn[ j ] = dyn[ j - i ] + dyn[ j ]
  24. fout << (dyn[s]/2) << endl;
  25. fout.close();
  26. return 0;
  27. }

-------------------------------------------------------

  1. USACO 2.3
  2. Longest Prefix
  3. 题目如下:
  4. 在生物学中,一些生物的结构是用包含其要素的大写字母序列来表示的。生物学家对于把长的序列分解成较短的(称之为元素的)序列很感兴趣。
  5. 如果一个集合 P 中的元素可以通过串联(允许重复;串联,相当于 Pascal 中的 “+” 运算符)组成一个序列 S,那么我们认为序列 S 可以分解为 P 中的元素。并不是所有的元素都必须出现。举个例子,序列 ABABACABAAB可以分解为下面集合中的元素:
  6. {A, AB, BA, CA, BBC}
  7. 序列 S 的前面 K 个字符称作 S 中长度为 K 的前缀。设计一个程序,输入一个元素集合以及一个大写字母序列,计算这个序列最长的前缀的长度。
  8. PROGRAM NAME: prefix
  9. INPUT FORMAT
  10. 输入数据的开头包括 1..200 个元素(长度为 1..10)组成的集合,用连续的以空格分开的字符串表示。字母全部是大写,数据可能不止一行。元素集合结束的标志是一个只包含一个 “.”的行。集合中的元素没有重复。接着是大写字母序列 S ,长度为 1..200,000 ,用一行或者多行的字符串来表示,每行不超过 76个字符。换行符并不是序列 S 的一部分。
  11. SAMPLE INPUT (file prefix.in)
  12. A AB BA CA BBC
  13. .
  14. ABABACABAABC
  15. OUTPUT FORMAT
  16. 只有一行,输出一个整数,表示 S 能够分解成 P 中元素的最长前缀的长度。
  17. SAMPLE OUTPUT (file prefix.out)
  18. 11

动规的基本思路:定义f[i],表示到目标字符串的第i个位置,是否还有连续的前缀。有,则把当前的i+length(si[k])记录下来;没有,则继续循环。Si[k]记录集合中的待选字符串。

  1. 示例程序如下:

  2. #include <stdio.h>
  3. / maximum number of primitives /
  4. #define MAXP 200
  5. / maximum length of a primitive /
  6. #define MAXL 10
  7. char prim[MAXP+1][MAXL+1]; / primitives /
  8. int nump;                / number of primitives /
  9. int start[200001];       / is this prefix of the sequence expressible? /
  10. char data[200000];       / the sequence /
  11. int ndata;                 / length of the sequence /
  12. int main(int argc, char **argv)
  13. {
  14.    FILE fout, fin;
  15.    int best;
  16.    int lv, lv2, lv3;
  17.    if ((fin = fopen("prim.in", "r")) == NULL)
  18. {
  19. perror ("fopen fin");
  20.    exit(1);
  21. }
  22.    if ((fout = fopen("prim.out", "w")) == NULL)
  23. {
  24. perror ("fopen fout");
  25. exit(1);
  26. }
  27.    / read in primitives /
  28.    while (1)
  29. {
  30. fscanf (fin, "%s", prim[nump]);
  31. if (prim[nump][0] != ‘.’) nump++;
  32. else break;
  33. }
  34.    / read in string, one line at a time /
  35.    ndata = 0;
  36.    while (fscanf (fin, "%s", data+ndata) == 1)
  37. ndata += strlen(data+ndata);
  38.    start[0] = 1;
  39.    best = 0;
  40.    for (lv = 0; lv < ndata; lv++)
  41. if (start[lv])
  42.     { / for each expressible prefix /
  43.    best = lv; / we found a longer expressible prefix! /
  44.    / for each primitive, determine the the sequence starting at
  45.       this location matches it /
  46.    for (lv2 = 0; lv2 < nump; lv2++)
  47.    {
  48.        for (lv3 = 0; lv + lv3 < ndata &   prim[lv2][lv3] &&
  49.     prim[lv2][lv3] == data[lv+lv3]; lv3++)
  50.       ;
  51. if (!prim[lv2][lv3]) / it matched! /
  52. start[lv + lv3] = 1; / so the expanded prefix is also expressive /
  53.    }
  54.     }
  55.    / see if the entire sequence is expressible /
  56.    if (start[ndata]) best = ndata;
  57.    fprintf (fout, "%in", best);
  58.    return 0;
  59. }

------------------------------------------------

  1. USACO 3.1
  2. Score Inflation
  3. 题目如下:
  4. 我们试着设计我们的竞赛以便人们能尽可能的多得分,这需要你的帮助。
  5. 我们可以从几个种类中选取竞赛的题目,这里的一个"种类"是指一个竞赛题目的集合,解决集合中的题目需要相同多的时间并且能得到相同的分数。
  6. 你的任务是写一个程序来告诉USACO的职员,应该从每一个种类中选取多少题目,使得解决题目的总耗时在竞赛规定的时间里并且总分最大。
  7. 输入包括竞赛的时间,M(1 <= M <= 10,000)和N,"种类"的数目1 <= N <= 10,000。
  8. 后面的每一行将包括两个整数来描述一个"种类":
  9. 第一个整数说明解决这种题目能得的分数(1 <= points <= 10000),第二整数说明解决这种题目所需的时间(1 <= minutes <= 10000)。
  10. 你的程序应该确定我们应该从每个"种类"中选多少道题目使得能在竞赛的时间中得到最大的分数。
  11. 来自任意的"种类"的题目数目可能任何非负数(0或更多)。
  12. 计算可能得到的最大分数。
  13. PROGRAM NAME: inflate
  14. INPUT FORMAT
  15. 第 1 行: M, N–竞赛的时间和题目"种类"的数目。  
  16. 第 2-N+1 行:   两个整数:每个"种类"题目的分数和耗时。
  17. SAMPLE INPUT (file inflate.in)
  18. 300 4
  19. 100 60
  20. 250 120
  21. 120 100
  22. 35 20
  23. OUTPUT FORMAT
  24. 单独的一行包括那个在给定的限制里可能得到的最大的分数。
  25. SAMPLE OUTPUT (file inflate.out)
  26. 605
  27. {从第2个"种类"中选两题,第4个"种类"中选三题}


  1. 示例程序如下:

  2. #include <fstream.h>
  3. ifstream fin("inflate.in");
  4. ofstream fout("inflate.out");
  5. const short maxm = 10010;
  6. long best[maxm], m, n;
  7. void
  8. main()
  9. {
  10. short i, j, len, pts;
  11. fin >> m >> n;
  12. for (j = 0; j <= m; j++)
  13.        best[j] = 0;
  14. for (i = 0; i < n; i++) {
  15.        fin >> pts >> len;
  16.        for (j = len; j <= m; j++)
  17.          if (best[j-len] + pts > best[j])
  18.             best[j] = best[j-len] + pts;
  19. }
  20. fout << best[m] << endl; // 由于数组元素不减,末元素最大
  21. }


--------------------------------------------


A Game

有如下一个双人游戏:N(2 <= N <= 100)个正整数的序列放在一个游戏平台上,两人轮流从序列的两端取数,取数后该数字被去掉并累加到本玩家的得分中,当数取尽时,游戏结束。以最终得分多者为胜。

编一个执行最优策略的程序,最优策略就是使自己能得到在当前情况下最大的可能的总分的策略。你的程序要始终为第二位玩家执行最优策略。

PROGRAM NAME: game1
INPUT FORMAT
第一行:   正整数N, 表示序列中正整数的个数。  
第二行至末尾: 用空格分隔的N个正整数(大小为1-200)。

SAMPLE INPUT (file game1.in)
6
4 7 2 9
5 2

OUTPUT FORMAT

只有一行,用空格分隔的两个整数: 依次为玩家一和玩家二最终的得分。

SAMPLE OUTPUT (file game1.out)
18 11

设得到一个子序列Qi,Qi+1,Qi+2,…,Qj-2,Qj-1,Qj;
由于后一个人按最佳策略取数,所以他必将取一个最大值f(i+1,j)或f(i,j-1),那么此时取数的人以后取得的数将是sum(i+1,j)-f(i+1,j)或是sum(i,j-1)-f(i,j-1),此时取数的人按最佳策略取数,他会取Qi或者Qj使得sum(i+1,j)-f(i+1,j)+Qi和sum(i,j-1)-f(i,j-1)+Qj二者能有较大值.这样从游戏的结束向游戏的开始推,可以得到总的最优解.


for i←n-1 downto 1 do
for j←i+1 to n do
    begin
      sum(i,j)←sum(i,j-1)+Qj
      f[i,j]←max{sum(i+1,j)+Qi-f(i+1,j),sum(i,j-1)+Qj-f(i,j-1)}
    end;

-----------------------------------------------------

  1. USACO 3.4
  2. Raucous Rockers
  3. 题目如下:
  4. 你刚刚得到了流行的“破锣摇滚”乐队录制的尚未发表的N(1 <= N <=20)首歌的版权。你打算从中精选一些歌曲,发行M(1 <= M <= 20)张CD。每一张CD最多可以容纳T(1 <= T<= 20)分钟的音乐,一首歌不能分装在两张CD中。
  5. 不巧你是一位古典音乐迷,不懂如何判定这些歌的艺术价值。于是你决定根据以下标准进行选择:
  6. 歌曲必须按照创作的时间顺序在CD盘上出现。
  7. 选中的歌曲数目尽可能地多。
  8. PROGRAM NAME: rockers
  9. INPUT FORMAT
  10. 第一行:   三个整数:N, T, M.  
  11. 第二行:   N个整数,分别表示每首歌的长度,按创作时间顺序排列。  
  12. SAMPLE INPUT (file rockers.in)
  13. 4 5 2
  14. 4 3 4 2
  15. OUTPUT FORMAT
  16. 一个整数,表示可以装进M张CD盘的乐曲的最大数目。
  17. SAMPLE OUTPUT (file rockers.out)
  18. 3

分析 动态规划

f[i, j, k]:前i首歌 前j个光盘 在当前时间限制为k时最多能放多少首歌。
当k>=a[i]时f[i,j,k]=max(f[i-1,j,k-a[i]] + 1, f[i-1,j,k]);当k<a[i]时f[i,j,k]=max(f[i,j-1,t], f[i-1,j,k])
i表示的是第i首歌,j表示的是第j个光盘,k表示的是要用的时间,
a[i]是第i首歌的时间,t表示的是光盘的最大空间。
其实他就是一个01背包类的问题,只不过我们要考虑一下当前的这个光盘是否可以放,然后在考虑放与不放。

#include<stdlib.h>
#include<stdio.h>
int main()
{
    int i1,i2,n,t,m,i,a[22],f[50][50][50]={0};
    FILE in;
    in=fopen("input12.txt","r");
    fscanf(in,"%d%d%d",&n,&t,&m);
    for(i=1;i<=n;i++)
        fscanf(in,"%d",&a[i]);
    for(i=1;i<=n;i++)
        for(i1=1;i1<=m;i1++)
            for(i2=0;i2<=t;i2++)
            {
                if(i2>=a[i])
                {
                    f[i][i1][i2]=f[i-1][i1][i2];
                    if(f[i][i1][i2]<f[i-1][i1][i2-a[i]]+1)
                        f[i][i1][i2]=f[i-1][i1][i2-a[i]]+1;
                }
                else
                {
                    f[i][i1][i2]=f[i][i1-1][t];
                    if(f[i][i1][i2]<f[i-1][i1][i2])
                        f[i][i1][i2]=f[i-1][i1][i2];
                }
            }
            printf("%d",f[n][m][t]);
            system("pause");
}

-----------------------------------------------------