2017清北学堂(提升组精英班)集训笔记——动态规划Part2

啊~到下午啦,咱们进入Part2!——一个简洁的开头html

咱们来探讨第一类问题——路径行走问题web

经典例题:方格取数(Luogu 1004)算法

设有 N*N 的方格图 (N<=9),咱们将其中的某些方格中填入正整数,而其余的方格中则放入数字 0
某人从图的左上角的 点出发,能够向下行走,也能够向右走,直到到达右下角的 点。在走过的路上,他能够取走方格中的数(取走后的方格中将变为数字 0)。
此人从 点到 点共走两次,试找出 条这样的路径,使得取得的数之和为最大。
与数字金字塔很相似?若是只走一次呢? 

数组

* 只走一次(仿照数字金字塔)记录 F[i][j] 为走到第 i 行第 j 列的最大值。
思考:转移的顺序?转移的方程? 
网站

* 问题:在这道题目当中咱们不能直接套用走一次的方法;一个方格只能被取走一次(也就是说每一个权值只能被取用一次)。 
- 考虑两条道路同时进行:状态 F[i][j][k][l] 来记录第一条路径走到(i,j),而第二条路径走到 (k,l) 的最大值。 
spa

* 转移方程:考虑逆推(我多是由哪些状态获得的)设计

在这里,咱们要保证这两个点所走的步数是相同的,那么这个状态才是有意义的,在这里没有这样算,不算也是对的,算了也不影响答案。。
- 每一个点能够往下走或者往右走;一共走到有 2*2=4 种可能性(时刻注意边界状况)
3d

 1 //T11:方格取数(DP/逆推)
 2 for(int i=1;i<=n;++i)
 3 for(int j=1;j<=n;++j)
 4 for(int k=1;k<=n;++k)
 5 for(int l=1;l<=n;++l)
 6 {
 7     //注意,若是走到了一块儿,只加一次
 8     int cost=a[i][j]+a[k][l]-a[i][j]*(i==k&&j==l);//若是两个位置是重叠的,就要减去重复的 
 9     //四种可能性;考虑:为何不加边界状况的判断?
10     f[i][j][k][l]=max(max(f[i-1][j][k-1][l],f[i-1][j][k][l-1]),max(f[i][j-1][k-1][l],f[i][j-1][k][l-1]))+cost;
11 }

 最长不降低子序列问题:code

经典例题:导弹拦截(Luogu 1020)orm

某国为了防护敌国的导弹袭击,发展出一种导弹拦截系统。可是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹可以到达任意的高度,可是之后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。因为该系统还在试用阶段,因此只有一套系统,所以有可能不能拦截全部的导弹。
输入导弹依次飞来的高度(雷达给出的高度数据是不大于 30000 的正整数),计算这套系统最多能拦截多少导弹,若是要拦截全部导弹最少要配备多少套这种导弹拦截系统。 
-389 207 155 300 299 170 158 65 

-最多可以拦截: 6;最少要配备: 2

* LIS:一个序列当中一段不降低的子序列。
* 这道题目中第一问要求咱们找到一段最长的单调降低的子序列(不管是上升仍是降低,可使用相似的算法解决)
* 状态:咱们用 F[i] 表明,i 位置为结尾的一段, 最长的降低子序列的长度
! 最优性:若是某段 [q1q2q3 ...qn] 是以qn结尾的最长降低子序列;那么去掉最后一个的序列 [q1q2q3...qn-1]依然是qn-1结尾的最长降低子序列。(一个这是一个全局最优,每一步的最优解构成全局最优,因此拆成部分仍是最优解,知足无后效性原则)

* 逆推:假设咱们须要求X 结尾最长降低子序列 F[X]
* 最优性可得,咱们除去最后一个位置(也就是 X),仍是一段最长降低子序列
* 那咱们能够枚举这个子序列的结尾 Y,最优值就是 F[Y]
! 但须要注意的是,必须保证A[X] < A[Y]X Y 要低,才知足降低的要求。
* 咱们从全部枚举的结果中找到一个最大的便可

例如:咱们看到这坨绿油油的图,咱们讨论F[8]做为谁的结尾,由图中咱们能够获得:

F[8]=F[4]+1(F[8]能够做为F[4]的结尾);  F[8]=F[6]+1(F[8]一样能够做为F[6]的结尾);  F[8]=F[7]+1(F[8]能够做为F[7]的结尾)

可是最长的仍是F[8]做为F[4]结尾,这时候最长,取max

* 注意到题目还须要计算若是要拦截全部导弹最少要配备多少套这种导弹拦截系统。’
* 能够直接观察获得,所求的答案至少为原题的最长不降低子序列
- 由于它们当中,任意两个都不可能被同一个导弹打中。
- 事实能够证实,这就是答案。—— 啊喂?不给个证实嘛→_→?那确定给啊!证实以下!

证实:由于假设一个导弹a被打中了,那么下次有比它高的导弹b,就没办法用打中a的导弹系统来打b,必须增长一个导弹系统,因此为最长不降低子序列长度。

一样地,咱们还能够运用极限思惟来考虑,有n颗导弹,它们的高度都是单调递增的,那么这时就必需要n个系统来拦截,因此为最长不降低子序列长度=n;

 1 //T12:导弹拦截(DP/LIS/逆推)
 2 int ansf=0,ansg=0;//记录全部的f(g)中的最优值
 3 //f计算降低子序列,g计算不降低子序列
 4 for(int i=1;i<=n;++i)
 5 {////枚举倒数第二个,寻找最长降低放到f中,最长不降低放到g中
 6     for(int j=1;j<i;++j)
 7     if(a[j]>a[i]) f[i]=max(f[i],f[j]);
 8     else g[i]=max(g[i],g[j]);
 9     ++f[i],++g[i];//加上本身的一个
10     ansf=max(ansf,f[i]);
11     ansg=max(ansg,g[i]);
12 }
13 cout<<ansf<<endl<<ansg<<endl;//输出答案

 问题变形:

经典例题:合唱队形(Luogu 1091)

*N位同窗站成一排,音乐老师要请其中的(N-K)位同窗出列,使得剩下的 位同窗排成合唱队形。
*合唱队形是指这样的一种队形:设 K 位同窗从左到右依次编号为1,2,…,K,他们的身高分别为T1T2,...,TK,则他们的身高知足T1<...<Ti>Ti+1>...>TK(1i≤ K)
* 你的任务是,已知全部 N 位同窗的身高,计算最少须要几位同窗出列,可使得剩下的同窗排成合唱队形。
186 186 150 200 160 130 197 220
最少须要 位同窗出列 

所谓合唱队形,就是要一个队列高度构成一个山峰的形状,有顶尖,两边单调递减。

咱们能够采用枚举每一个人为顶尖,从那个定点开始,向左、右寻找单调递减序列的最大和,这样。。。真的很麻烦。

* 问题转化:最少的同窗出列 -> 尽可能多的同窗留在队列
* LIS 的联系:若是肯定了中间的“顶尖” ,两侧就是“单调上升” 和“单调降低” 的。 
           

 * 状态设计F[i] G[i](预先处理)
- F[i]i 为端点,左侧的最长的上升子序列长度。
- G[i]i 为端点右侧的最长的降低子序列长度。
这个思路就是要咱们找到每一个以第i个点为末端的最长上升子序列和最长降低子序列,最终咱们枚举每个点i访问f[i]、g[i],找出二者相加最大值便可。

一样,咱们还有另外一种思路,找到了最长上升子序列和最长降低子序列,两个序列合并,去掉中间重复的元素(出现顶尖),即为答案。(这里我就不粘代码了,详见我以前写这个题的博客吧→_→)

 1 //T13:合唱队形(DP/LIS/逆推)
 2 for(int i=1;i<=n;++i) cin>>a[i],f[i]=g[i]=1;
 3 for(int i=1;i<=n;++i)
 4 for(int j=1;j<i;++j)
 5     if(a[i]>a[j]) f[i]=max(f[i],f[j]+1);
 6 for(int i=n;i;--i)//g的计算从反方向进行枚举
 7     for(int j=n;j>i;--j)
 8         if(a[i]>a[j]) g[i]=max(g[i],g[j]+1);
 9 int ans=0;
10 /*把最长上升和最长降低的一部分部分拼在一块儿求总长度最大*/
11 for(int i=1;i<=n;++i)//枚举每个顶点为顶尖
12 {
13     ans=max(ans,f[i]+g[i]-1);//"-1"表示减掉顶尖这个重复计算的点 
14 }

最长公共子序列:

经典例题:排列LCS问题(Luogu 1439)

* 给出 1-n 的两个排列 P1 P2,求它们的最长公共子序列。
* 公共子序列:既是 P1 的子序列,也是 P2 的子序列。
- 3 2 1 4 5
- 1 2 3 4 5
- 最长公共子序列 (LCS)3([1 4 5])

* LCS:两个序列的最长公共子序列
* 状态:咱们用 F[i][j] 表明,前一个序列i 位置为结尾,后一个序列以 位置为结尾,它们的最长公共子序列
! 最优性:若是某段 [q1q2q3...qn] 是分别以i,j
结尾的最长公共子序列;那么去掉最后一个的序列 [q1q2q3...qn-1],依然是以 11结尾的最长公共子序列。 

 * 逆推:假设咱们须要求两个序列分别以 i,j 结尾最长公共子序列F[i][j],接下来咱们能够分几种状况讨论:
- A[i] 不在公共子序列中,那么长度则等于 F[i-1][j]
- B[j] 不在公共子序列中,那么长度则等于 F[i][j-1]
- A[i] B[j] 都在子序列中,而且二者匹配,那么长度等于F[i-1][j-1]+1
* 咱们从全部枚举的结果中找到一个最大的便可。 

* 逆推:假设咱们须要求两个序列分别以 i,j 结尾最长公共子序列F[i][j],可能的三种状况: 

1 //T16:排列LCS问题(DP/LCS/50分数据规模限制)
2 for(int i=1;i<=n;++i)
3     for(int j=1;j<=n;++j)
4     {
5         //分三种状况进行讨论
6         f[i][j]=max(f[i-1][j],f[i][j-1]);//若是两个相同,娶一个最大值 7         if(p[i]==q[j]) f[i][j]=max(f[i][j],f[i-1][j-1]+1);
8     }
9 int ans=f[n][n];

仅仅在这个问题中,LCS是能够转化为LIS的:

* 假定某一个序列为 [1 2 3 ... N],那么答案则是另外一个序列的 LIS;(由于知足严格的单调性质)
- 3 2 1 4 5
- 1 2 3 4 5
* 但若是两个序列都不是 [1 2 3 ... N] 呢?经过转化使一个序列变成它(过程为:咱们把第一个序列的第一个数5变成1,第二个序列的1变成5;把第一个序列的第二个数3变为2,第二个序列的2变为3,以此类推...),而答案不变。
- 5 3 4 1 2 -> 1 2 3 4 5
- 3 5 1 2 4 -> 2 1 4 5 3

这种转换只能用在所给序列有不重复元素,长度相同,若是不知足,就会出现重复,会出错。

问题变形:

经典例题:字串距离(Luogu 1279)

* 设有字符串 X,咱们称在 X 的头尾及中间插入任意多个空格后构成的新字符串为 的扩展串,如字符串 为” abcbcd”,则字符串abcb□cd”,“□a□bcbcd□”和“abcb□cd□”都是 的扩展串,这里”表明空格字符。
* 若是 A1 是字符串 A 的扩展串, B1 是字符串 B 的扩展串, A1 B1具备相同的长度,那么我扪定义字符串 A1 与 B1 的距离为相应位置上的字符的距离总和,而两个非空格字符的距离定义为它们的 ASCII 码的的字符的距离总和,而两个非空格字符的距离定义为它们的 ASCII 码的差的绝对值,而空格字符与其余任意字符之间的距离为已知的定值 K,空格字符与空格字符的距离为 0。在字符串 A、 的全部扩展串中,一定存在两个等长的扩展串 A1、 B1,使得 A1 与 B1 之间的距离达到最小,咱们将这一距离定义为字符串 A、 的距离。
* 请你写一个程序,求出字符串 AB 的距离。  

- cmc
- snmn
- 2
- 距离为:10
  

* 状态设计:仿照最长公共子序列,咱们设计状态 F[i][j] 为前一个序列以 结尾,后一个序列以 结尾的最小距离;一样也有如下三种状况
- A[i] 与空格匹配:距离为 F[i-1][j] + K(K 为到空格的距离)
- B[j] 与空格匹配:距离为 F[i][j-1] + K
- A[i] B[j] 匹配:距离为 F[i-1][j-1] + |A[i] - B[j]|;(ASCII 码的差的绝对值)

 1 //T17:字串距离(DP/LCS)
 2 //初值:在遇到最小值的问题,必定要当心初值的处理
 3 f[0][0]=0;
 4 for(int i=1;i<=n;++i) f[i][0]=i*k;
 5 for(int j=1;j<=m;++j) f[0][j]=j*k;
 6 for(int i=1;i<=n;++i)//n为第一个串的长度
 7 for(int j=1;j<=m;++j)//m为第二个串的长度
 8 {
 9     //三种状况
10     f[i][j]=min(f[i-1][j],f[i][j-1])+k;
11     f[i][j]=min(f[i][j],f[i-1][j-1]+abs(p[i-1]-q[j-1]));
12 }
13 int ans=f[n][m];

 二维平面问题:

 经典例题:家的范围(Luogu 2733)

  农民约翰在一片边长是 N (2 <= N <= 250) 英里的正方形牧场上放牧他的奶牛。 (由于一些缘由,他的奶牛只在正方形的牧场上吃草。 ) 遗憾的是, 他的奶牛已经毁坏一些土地。 ( 一些 1 平方英里的正方形)
  农民约翰须要统计那些能够放牧奶牛的正方形牧场 (至少是 2x2 的, 在这些较大的正方形中没有一个点是被破坏的,也就是说,全部的点都是“ 1” )。
  你的工做要在被供应的数据组里面统计全部不一样的正方形放牧区域(>=2x2) 的个数。固然,放牧区域多是重叠。
如右图所示:以二、三、4为边长的正方形共有十、四、1个,不妨转换一下思路:咱们只考虑以(x,y)这个点能构成的最大正方形是到多大?这样把问题化简就好了。

*状态设计:设状态 F[i][j] 为以 (i,j) 位置为右下角,最大的一个正方形区域。

* 考虑位置 F[i-1][j] 与 F[i][j-1];
- 则有 F[i][j] <= F[i-1][j] + 1; F[i][j] <= F[i][j-1] + 1;

上图中,*号位置(i,j)的大小由上方和左方决定,*号位置的F[i][j]=4而≠5,由于*号的值不能超过4也不能超过5,因此只能取4,要取F[i-1][j] + 一、F[i][j-1] + 1二者中较小的一个。

很明显, F[i][j-1] 与 F[i-1][j] 中较小值限制了最大区域的边长。

* 但在某些状况下,仅考虑 F[i][j-1] 与 F[i-1][j] 显然不全面,还须要考虑 F[i-1][j-1]。

* 因此咱们能够获得最终的表达式: F[i][j] = MIN(F[i-1][j],F[i][j-1], F[i-1][j-1]) + 1 (当 (i,j) 不为障碍时)

 1 //T18:家的范围(DP)
 2 //边界初值
 3 for(int i=0;i<n;++i) f[i][0]=(a[i][0]=='1');//t[i]表示最大边长为i的正方形的个数
 4     for(int j=0;j<n;++j) f[0][j]=(a[0][j]=='1');
 5         for(int i=1;i<n;++i)
 6             for(int j=1;j<n;++j) 
 7                 if(a[i][j]=='1')//若是是非障碍
 8                 {//计算
 9                     f[i][j]=min(min(f[i-1][j],f[i][j-1]),f[i-1][j-1])+1;
10                     t[f[i][j]]++;//当前这个规格为i的正方形的个数++
11                 }
12 for(int i=n;i;i--)/*统计全部方形的数目——(t[i]已经算出来了以(i,j)这个点为右下角可以成的最大正方形
13                   总数,可是题目中要求总和,因此这个for就是求以(i,j)为右下角的正方形总数),
14                   例如:以(i,j)为右下角的4*4正方形能够提供一个以(i,j)为右下角的3*3正方形,
15                   一个以(i,j)为右下角的2*2正方形…以此类推,倒着累加便可*/
16     t[i-1]+=t[i];
17 for(int i=2;i<=n;++i)
18     if(t[i])//输出结果
19         cout<<i<<" "<<t[i]<<endl;

区间动态规划:

经典例题:游戏 A Game(Luogu 2734)

  有以下一个双人游戏:N(2 <= N <= 100) 个正整数的序列放在一个游戏平台上,游戏由玩家 1 开始,两人轮流从序列的任意一端取一个数,取数后该数字被去掉并累加到本玩家的得分中,当数取尽时,游戏结束。以最终得分多者为胜。
  编一个执行最优策略的程序,最优策略就是使玩家在与最好的对手对弈时,能获得的在当前状况下最大的可能的总分的策略。你的程序要始终为第二位玩家执行最优策略。

* 状态设计方法为: F[i][j] 表示倘若只用第 i 个数与第 j 个数之间的数进行游戏, 先手能得到的最高的分为多少。
* 咱们能够根据先手取左边仍是取右边来决定哪一种状况得分高。
! 当先手取完一件后, 前后手发生交换,而问题是相似的,以下图:

在左图中,咱们已经计算出了七、二、九、五、2中先手能得到的最大值为14,后手为11,当先手取第一个数4时,先手=后手+4,后手=先手(发生了交换)。

一样地,在右图中,咱们已经计算出了四、七、二、九、5中先手能得到的最大值为11,后手为16,当先手取最后一个数2时,先手=后手+2,后手=先手(一样发生了当前max值的交换)。

- 考虑: 如何决定状态转移顺序?先算哪些,后算哪些?如何书写状态转移方程?
在例子中,咱们要计算长度为6的区间内的最优值,咱们必需要算出左边长度为5的、右边长度为5的两个区间的最优值,因此能够先算长度为1的区间,再算长度为2的区间,再算长度为3的区间…就能够获得最大区间最优值。

 1 //T19:游戏
 2 cin>>n;
 3 for(int i=1;i<=n;++i)
 4     cin>>a[i],
 5 s[i]=s[i-1]+a[i];//s表明前缀和
 6 for(int i=1;i<=n;++i) f[i][i]=a[i];//初值(先手最后只剩下一个数,就是我本身)
 7 for(int k=2;k<=n;++k)//按照序列的长度进行枚举(k为区间长度) 
 8     for(int i=1,j;i+k-1<=n;++i)
 9     {
10         j=i+k-1;
11         f[i][j]=max(s[j]-s[i]-f[i+1][j]+a[i],s[j-1]-s[i-1]-f[i][j-1]+a[j]);//二者取较大值=max(先取最左边先手最大得分,先取最右边先手最大得分) 
12     }             //左边总和-先手玩家得分+本身当前取得的分数=总得分
13 cout<<f[1][n]<<" "<<s[n]-f[1][n]<<endl;//输出答案

经典例题:加分二叉树(Luogu 1040)

  设一个 n 个节点的二叉树 tree 的中序遍历为( 1,2,3,…,n),其中数字 1,2,3,…,n 为节点编号。每一个节点都有一个分数(均为正整数),记第 i 个节点的分数为 di, tree 及它的每一个子树都有一个加分,任一棵子树 subtree(也包含 tree 自己)的加分计算方法以下:
  subtree 的左子树的加分 × subtree 的右子树的加分+ subtree 的根的分数。
  若某个子树为空,规定其加分为 1,叶子的加分就是叶节点自己的分数,不考虑它的空子树。
  试求一棵符合中序遍历为( 1,2,3,…,n)且加分最高的二叉树 tree。要求输出;
1.tree 的最高加分:
2.tree 的前序遍历:
- 5 7 1 2 10
- 答案 1: 145
- 答案 2: 3 1 2 4 5
设 F[i][j] 为只用第 i 个数到第 j 个数构成的加分树的最大权值。下图为样例解释:

牢记一个二叉树的性质:中序遍历时候,左右子树必定在根节点左右两边

* 枚举根节点,这样就化成了左子树和右子树的问题,求最优解便可。
* F[i][j] = MAX ( F[i][k-1] * F[k+1][j] + A[k] )(左×右+根k本身自己权值)

 1 //T25:加分二叉树
 2 for(int i=1;i<=n;++i) f[i][i]=a[i];//赋初值(只有一个叶子节点,根就是本身) 
 3 for(int i=0;i<=n;++i) f[i+1][i]=1;
 4 for(int k=1;k<n;++k)
 5     for(int i=1;i+k<=n;++i)
 6     {
 7         int j=i+k;
 8         for(int l=i;l<=j;++l) f[i][j]=max(f[i][j],f[i][l-1]*f[l+1][j]+a[l]);//枚举根节点 
 9     }
10 int ans=f[1][n];

* 问题: 如何求出树的前序遍历(树的形态)

咱们另外记录一个辅助数组 G[i][j],表明 F[i][j] 取最大值的时候,根节点是什么,这样就能够经过递归来求出树的前序遍历。

 1 for(int i=1;i<=n;++i) f[i][i]=a[i],g[i][i]=i;//边界值(只有一个叶子节点,根就是本身) 
 2 for(int i=0;i<=n;++i) f[i+1][i]=1;//预处理空节点,保证不出错,一个根节点没有左子树,把左子树标记为1 
 3 for(int k=1;k<n;++k)//k:区间长度 
 4 for(int i=1;i+k<=n;++i)
 5 {
 6     int j=i+k;//j:末尾节点 
 7     for(int l=i;l<=j;++l)
 8     {
 9         long long t=f[i][l-1]*f[l+1][j]+a[l];
10         if(t>f[i][j])//记录最优的根
11         {
12             f[i][j]=t;
13             g[i][j]=l;
14         } 
15     }
16 }
 1 //T25:加分二叉树
 2 //递归输出x到y这个树的前缀遍历
 3 void dfs(int x,int y)
 4 {
 5     if(x>y) return;
 6     int l=g[x][y];//l为根
 7     cout<<l<<" ";//先输出l
 8     /*=====================*///再输出子树的值
 9     dfs(x,l-1);//
10     dfs(l+1,y);//
11     /*=====================*/
12 }
13 ...
14 //输出答案——整棵树
15 dfs(1,n);

过程型状态划分:

经典例题:传球游戏(Luogu 1057)

  上体育课的时候,小蛮的老师常常带着同窗们一块儿作游戏。此次,老师带着同窗们一块儿作传球游戏。
  游戏规则是这样的: n 个同窗站成一个圆圈,其中的一个同窗手里拿着一个球,当老师吹哨子时开始传球,每一个同窗能够把球传给本身左右的两个同窗中的一个(左右任意),当老师在此吹哨子时,传球中止,此时,拿着球没有传出去的那个同窗就是败者,要给你们表演一个节目。
  聪明的小蛮提出一个有趣的问题:有多少种不一样的传球方法可使得从小蛮手里开始传的球,传了 m 次之后,又回到小蛮手里。两种传球方法被视做不一样的方法,当且仅当这两种方法中,接到球的同窗按接球顺序组成的序列是不一样的。好比有三个同窗 1 号、 2 号、 3 号,并假设小蛮为 1号,球传了 3 次回到小蛮手里的方式有 1->2->3->1 和 1->3->2->1,共 2 种。

* 分析:这道题目十分容易用搜索解决。 为何?
* 由于题目已经明确给定了过程:传球的次数。
* 由于这个过程是必定按照顺序进行的,因此能够直接写出状态:
* 设状态 F[i][j] 为传到第 i 次,如今在第 j 我的手上的方案数。
* 很显然 F[i] 只和 F[i-1] 有关;由于题目已经规定好了传递顺序。
! 这一类的过程型问题只须要找出事情发展的顺序,就能够很简单的写出状态与转移方程。

 1 //T26:传球游戏
 2 f[0][1]=1;//赋初值
 3 for(int i=1;i<=m;++i)
 4 {
 5     f[i][1]=f[i-1][2]+f[i-1][n];//头与尾须要特殊处理(第1号从2号和n号传过来)
 6     f[i][n]=f[i-1][n-1]+f[i-1][1];
 7     for(int j=2;j<n;++j)//转移只和i-1有关系(由于我已经规定好了发展顺序)
 8         f[i][j]=f[i-1][j-1]+f[i-1][j+1];//能够在j-一、j+1手里(加法原理)
 9 }
10 int ans=f[m][1];

经典例题:乌龟棋(Luogu 1541)

  乌龟棋的棋盘是一行 N 个格子,每一个格子上一个分数(非负整数)。棋盘第 1 格是惟一的起点,第 N 格是终点,游戏要求玩家控制一个乌龟棋子从起点出发走到终点。
  乌龟棋中 M 张爬行卡片,分红 4 种不一样的类型( M 张卡片中不必定包含全部 4 种类型的卡片,见样例),每种类型的卡片上分别标有 一、 二、 三、4 四个数字之一,表示使用这种卡片后,乌龟棋子将向前爬行相应的格子数。游戏中,玩家每次须要从全部的爬行卡片中选择一张以前没有使用过的爬行卡片,控制乌龟棋子前进相应的格子数,每张卡片只能使用一次。
  游戏中,乌龟棋子自动得到起点格子的分数,而且在后续的爬行中每到达一个格子,就获得该格子相应的分数。玩家最终游戏得分就是乌龟棋子从起点到终点过程当中到过的全部格子的分数总和。

- 棋盘: 6 10 14 2 8 8 18 5 17
- 卡片: 1 3 1 2 1
- 答案: 73 = 6+10+14+8+18+17

很明显,用不一样的爬行卡片使用顺序会使得最终游戏的得分不一样,小明想要找到一种卡片使用顺序使得最终游戏得分最多。如今,告诉你棋盘上每一个格子的分数和全部的爬行卡片,你能告诉小明,他最多能获得多少分吗?

* 思考:如何进行搜索?状态该如何设计?
* DFS(x,c1,c2,c3,c4) 为当前在第 x 个格子上, ci 表明标有数字i 的卡片有多少张。
* 因而能够直接写出状态 F[i][a][b][c][d],与状态是一一对应的,表示该状态下, 最大的权值是多少(当前第i个格子,四种牌分别用了i,j,k,l张的状况下,能达到的最优值)。
* 因而,能够和 F[i-1],F[i-2]…进行联系

 1 //T27:乌龟棋(我把i省略了,其实没什么用,加上只是用来判断是否越界而已)
 2 for(int i=0;i<=a;i++)
 3     for(int j=0;j<=b;j++)
 4         for(int k=0;k<=c;k++)
 5             for(int l=0;l<=d;l++)
 6             {
 7                 if(i!=0)f[i][j][k][l]=max(f[i][j][k][l],f[i-1][j][k][l]+s[i+j*2+k*3+l*4]);
 8                 if(j!=0)f[i][j][k][l]=max(f[i][j][k][l],f[i][j-1][k][l]+s[i+j*2+k*3+l*4]);
 9                 if(k!=0)f[i][j][k][l]=max(f[i][j][k][l],f[i][j][k-1][l]+s[i+j*2+k*3+l*4]);
10                 if(l!=0)f[i][j][k][l]=max(f[i][j][k][l],f[i][j][k][l-1]+s[i+j*2+k*3+l*4]);
11             }

最近发现一些网站盗用个人blog,这实在不能忍(™把关于个人名字什么的所有删去只保留文本啥意思。。)!!但愿各位转载引用时请注明出处,谢谢配合噢~

原博客惟一地址:http://www.cnblogs.com/geek-007/p/7197439.html

相关文章
相关标签/搜索