【待重置】dp专题

来源:互联网 发布:dos和windows的区别 编辑:程序博客网 时间:2024/05/16 11:06

标准背包代码

#include<stdio.h>#include<string.h>#include<math.h>#include<stdlib.h>#include<iostream>#include<string>#include<algorithm>using namespace std;#define MaxSize 3405int main(){    int n,m,i,j,p[MaxSize],g[MaxSize],f[12885];    while(~scanf("%d%d",&n,&m))    {        for(i=0;i<n;i++)            scanf("%d%d",&p[i],&g[i]);        memset(f,0,sizeof(f));        for(i=0;i<n;i++)        {            for(j=m;j>=p[i];j--)             f[j]=max(f[j],f[j-p[i]]+g[i]);/*这里如果for(j=p[i];j<=m;j++)就不可以,因为在下面f[j] max更新的时候,我们发现f[j]的更新值要由f[j-p[i]]决定(此轮更新时需要的f[j-p[i]]状态之前还没遇到第i个物品的),而j>j-p[i],即大角标的更新值由小角标决定。所以如果从小到大循环的话,在这一轮j循环中,小角标就比大角标先更新,而后面大角标更新的时候用到的小角标已经是此轮中更新过的,就会出问题。因为此轮更新时需要的f[j-p[i]]状态之前还没遇到第i个物品的。如果从小到大循环,假设之前小角标的更新时候已经选了这个物品,这里又选,所以这种情况只适用于有同一个物品有多个的情况,而不是普通背包*/        }        printf("%d\n",f[m]);    }  return 0;}//FROM CJZ




经典模型之最大正方形


题目描述:给你一幅矩阵图,#表示边长为1的单位正方形,0表示空,求这幅图里面最大的正方形边长。

0 0 # # 0 0
# # # # # 0
# # # # # #
0 0 # # # 0
# # 0 # 0 0

这里的动归思想就是:如果当前点是#,则有dp[i][j]=min(dp[i-1][j],dp[i-1][j-1],dp[i][j-1])+1;

dp[i][j]表示:以这个点为右下角结尾的正方形的最大边长

证明:


如图1,这里[i][j]的 上、左上、左 三个dp值中最小的是左上的,为2。因为我们知道它的边长是三者中是最小(记为a),因为其他两者的边长都比它大,所以边长差值至少为1(且也只能为1,后面会证明,这里先不管)。要使dp[i][j]=最小者边长+1,现在我们就要拼出来一个正方形,使得左上角是dp[i-1][j-1],右下角是dp[i][j](因为当前点是#,边长为1,要得到一个正方形使其边长为左上边长+1,即可看做左上正方形向右下拓展一个单位)。现在的问题就是看要拼出正方形需要拓展的部分是不是也全是#。这个结构可以看作三者最初的右下顶点都是重合的,那么那两者按着各自的方向(下和右)位移一个单位。且我们知道位移之后肯定能将最小者包含(因为边长差大于等于1嘛,你只移一个单位最多就是边重合嘛,又不会超出去)。所以这样想就能推导出拓展的部分都是#了。(我知道描述得云里雾里的,但是讲到这里自己就应该能明白了,这种只能意会不能言传的东西我都花了一个上午鼓捣画图整出来的)。


同理,这张图慢慢想,根据三者边长的关系,脑壳头放下电影就清楚了。


前面提到,差边长值必定是1,为什么呢?因为看图2,假如差值大于1,那么最小的那个dp就能被差值大于1的那个正方形边不重合地囊括进去,所以此时的dp就不只是原来的2了,也就矛盾了。


川大15届校赛是这个模型的模板题,然而数据太水,被我们暴力过了Or2......。西科大12届校赛在此基础上有点变形,下面贴一下。


小Y有一个矩阵,特别地,矩阵长宽相等,且矩阵中每个元素为0或者1,现在有一个简单的问题: 
小Y想知道矩阵中有多少个边长为k的子矩阵满足其元素全为1,你能帮他解决这个问题吗? 
Description
第一行输入一个整数T,表示测试数据组数,约10组 
对于每组数据,先输入两个整数n,m(1<=n<=1000,1<=m<=1000),表示矩阵的边长和询问的个数, 
接下来输入n行表示题目所述矩阵,然后m行每行输入一个整数k(1<=k<=1000),表示询问子矩阵的边长。 
Input
对于每组数据,输出m行,每行对应一个询问,表示满足条件的边长为k的子矩阵的个数。 
Output
1
2
3
4
5
6
7
1
2 3
10
11
1
2
3
Sample Input
1
2
3
3
0
0
代码:

#include <iostream>#include <cstdio>#include <cstring>using namespace std;#define ll long long#define INF 0x3f3f3f3f#define N 1010int sum[N];int dp[N][N];void solve(){  int T;  int n, m, k;  scanf("%d", &T);  while(T--)    {      scanf("%d%d", &n, &m);      memset(dp, 0, sizeof(dp));      memset(sum, 0, sizeof(sum));      for(int i = 1; i <= n; i++)        {          for(int j = 1; j <= n; j++)            {              scanf("%1d", &dp[i][j]);            }        }      for(int i = 1; i <= n; i++)        {          for(int j = 1; j <= n; j++)            {              if(dp[i][j] == 0)                continue;              else                {                  dp[i][j] = min(min(dp[i - 1][j], dp[i][j - 1]),dp[i - 1][j - 1]) + 1;                  sum[dp[i][j]]++;                }            }        }      int ans;      while(m--)        {          ans = 0;          scanf("%d", &k);          for(int i = k; i <= n; i++)            ans+=sum[i];          printf("%d\n", ans);        }    }}int main(){  solve();  return 0;}



反向背包

题目:POJ 3628

Description

Farmer John recently bought another bookshelf for the cow library, but the shelf is getting filled up quite quickly, and now the only available space is at the top.

FJ has N cows (1 ≤ N ≤ 20) each with some height of Hi (1 ≤ Hi ≤ 1,000,000 - these are very tall cows). The bookshelf has a height of B (1 ≤ B ≤S, where S is the sum of the heights of all cows).

To reach the top of the bookshelf, one or more of the cows can stand on top of each other in a stack, so that their total height is the sum of each of their individual heights. This total height must be no less than the height of the bookshelf in order for the cows to reach the top.

Since a taller stack of cows than necessary can be dangerous, your job is to find the set of cows that produces a stack of the smallest height possible such that the stack can reach the bookshelf. Your program should print the minimal 'excess' height between the optimal stack of cows and the bookshelf.

Input

* Line 1: Two space-separated integers: N and B
* Lines 2..N+1: Line i+1 contains a single integer: Hi

Output

* Line 1: A single integer representing the (non-negative) difference between the total height of the optimal set of cows and the height of the shelf.

Sample Input

5 1631356

Sample Output

1

思路:所有奶牛的高度之和 - 书架高度 = 背包容量,转化为普通背包问题。





2.最大上升子序列(二分+dp)


题目:poj 3903


#include<stdio.h>#include<string.h>#include<math.h>#include<stdlib.h>#include<iostream>#include<string>#include<algorithm>using namespace std;int main()//手工模拟一遍比较好理解,实践是检验真理的唯一标准!一切神奇皆在手工操作中体现。{  int n, a, mid, b, len;  int num[100005];//元素  int s[100005];//s[len]代表长度为len时,末尾元素的最小值  while(~ scanf("%d",&n))    {      len = 0;      s[0] = -1;            for (int i = 1; i <= n; i++)        {          scanf("%d", &num[i]);        }      for (int i = 1; i <= n; i++)//不要功利(指一遇到num[i]就考虑要不要加入最长序列),先不管最长子序列,就老老实实优化更新s表,最后再说找最长的len        {              a = 1;//这里要知道一点,s[n-k] 必定小于s[n]              b = len;//记清楚是len不是n              while(a <= b)//二分法找到恰好使S[i]小于当前元素的角标最大值(即最大序列长度)                {                  mid = (a + b) / 2;                  if (s[mid] < num[i])                    {                      a = mid + 1;//###最后一步时,这里已经让a+1                    }                  else                    {                      b = mid - 1;                    }                }//有可能找到的是len-1(如果是这样,那么s[len]就更新了),也有可能是更前面的(如果是这样,就为后面做铺垫)              s[a] = num[i];//这里直接用a而不是a+1做下标的原因见###              len=max(len,a);//记录表内最长的子序列长度        }      printf("%d\n", len);    }  return 0;}




举一反三

Description

The aspiring Roy the Robber has seen a lot of American movies, and knows that the bad guys usually gets caught in the end, often because they become too greedy. He has decided to work in the lucrative business of bank robbery only for a short while, before retiring to a comfortable job at a university. 


For a few months now, Roy has been assessing the security of various banks and the amount of cash they hold. He wants to make a calculated risk, and grab as much money as possible. 


His mother, Ola, has decided upon a tolerable probability of getting caught. She feels that he is safe enough if the banks he robs together give a probability less than this.
 

Input

The first line of input gives T, the number of cases. For each scenario, the first line of input gives a floating point number P, the probability Roy needs to be below, and an integer N, the number of banks he has plans for. Then follow N lines, where line j gives an integer Mj and a floating point number Pj . 
Bank j contains Mj millions, and the probability of getting caught from robbing it is Pj .
 

Output

For each test case, output a line with the maximum number of millions he can expect to get while the probability of getting caught is less than the limit set. 

Notes and Constraints 
0 < T <= 100 
0.0 <= P <= 1.0 
0 < N <= 100 
0 < Mj <= 100 
0.0 <= Pj <= 1.0 
A bank goes bankrupt if it is robbed, and you may assume that all probabilities are independent as the police have very low funds.
 

Sample Input

30.04 31 0.022 0.033 0.050.06 32 0.032 0.033 0.050.10 31 0.032 0.023 0.05
 

Sample Output

246
 

代码:

#include<stdio.h>#include<string.h>#include<math.h>#include<stdlib.h>#include<iostream>#include<string>#include<algorithm>using namespace std;#define MaxSize 105int m[MaxSize];double p[MaxSize],dp[10005];//dp[j]表示偷j元不被抓的概率int main(){//自己动手模拟理解一下  int t,n,i,j,sum;  double P;  scanf("%d",&t);  while(t--)    {      scanf("%lf%d",&P,&n);      sum = 0;      P = 1 - P;      for(i = 0; i < n; i++)        {          scanf("%d%lf",&m[i],&p[i]);          sum += m[i];          p[i] = 1 - p[i];        }      memset(dp,0,sizeof(dp));      /*想偷j元,可是偷不到那么多,那么这种情况就不能考虑,      由于取的是max,所以就令其为0,      让它在比较大小的时候被pass掉*/      dp[0] = 1;//一定要注意这个初始化      for(i = 0; i < n; i++)//还没遇到的银行偷不成,当然dp为0啊        for(j = sum; j >= m[i]; j--)            dp[j] = max(dp[j],dp[j-m[i]]*p[i]);//现在多了一个银行目标,就问你偷不偷            //如果不偷第i家银行,存活概率表保持不变,不更新,即:            //不偷的话,dp[原来最多偷到的钱+p[i]]自然还是为0;而在j在“原来最多偷到的钱”之内的dp值还是不变            //偷第i家银行存活的概率=不偷它的存活概率*偷了它不被抓的概率(越来越小)      for(i = sum; i >= 0&&dp[i] < P; i--);      printf("%d\n",i);    }  return 0;}




Description

Speakless很早就想出国,现在他已经考完了所有需要的考试,准备了所有要准备的材料,于是,便需要去申请学校了。要申请国外的任何大学,你都要交纳一定的申请费用,这可是很惊人的。Speakless没有多少钱,总共只攒了n万美元。他将在m个学校中选择若干的(当然要在他的经济承受范围内)。每个学校都有不同的申请费用a(万美元),并且Speakless估计了他得到这个学校offer的可能性b。不同学校之间是否得到offer不会互相影响。“I NEED A OFFER”,他大叫一声。帮帮这个可怜的人吧,帮助他计算一下,他可以收到至少一份offer的最大概率。(如果Speakless选择了多个学校,得到任意一个学校的offer都可以)。 
 

Input

输入有若干组数据,每组数据的第一行有两个正整数n,m(0<=n<=10000,0<=m<=10000) 
后面的m行,每行都有两个数据ai(整型),bi(实型)分别表示第i个学校的申请费用和可能拿到offer的概率。 
输入的最后有两个0。 
 

Output

每组数据都对应一个输出,表示Speakless可能得到至少一份offer的最大概率。用百分数表示,精确到小数点后一位。 
 

Sample Input

10 34 0.14 0.25 0.30 0
 

Sample Output

44.0%

Hint

 You should use printf("%%") to print a '%'.          
 

代码:

#include<stdio.h>#include<string.h>#include<math.h>#include<stdlib.h>#include<iostream>#include<string>#include<algorithm>using namespace std;#define MaxSize 10005int main(){    int n,m,i,j,p[MaxSize];    double r[MaxSize],f[MaxSize];    while(scanf("%d%d",&n,&m)!=EOF)    {      if(m==0&&n==0)          break;      for(i=0;i<m;i++)      {          scanf("%d%lf",&p[i],&r[i]);          r[i]=1-r[i];  //不能得到Offer的概率      }      for(i=0;i<=n;i++)          f[i]=1.0;  //初始化为1          for(i=0;i<m;i++)            for(j=n;j>=p[i];j--)              if(f[j]>f[j-p[i]]*r[i])                  f[j]=f[j-p[i]]*r[i];//求最小的不能得到的f[n]              printf("%.1lf%%\n",(1-f[n])*100);              //for(i=0;i<=n;i++)printf("%lf ",f[i]);    }    return 0;}





Labyrinth

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 152    Accepted Submission(s): 76

Problem Description
度度熊是一只喜欢探险的熊,一次偶然落进了一个m*n矩阵的迷宫,该迷宫只能从矩阵左上角第一个方格开始走,只有走到右上角的第一个格子才算走出迷宫,每一次只能走一格,且只能向上向下向右走以前没有走过的格子,每一个格子中都有一些金币(或正或负,有可能遇到强盗拦路抢劫,度度熊身上金币可以为负,需要给强盗写欠条),度度熊刚开始时身上金币数为0,问度度熊走出迷宫时候身上最多有多少金币?
 

Input
输入的第一行是一个整数T(T < 200),表示共有T组数据。
每组数据的第一行输入两个正整数m,n(m<=100,n<=100)。接下来的m行,每行n个整数,分别代表相应格子中能得到金币的数量,每个整数都大于等于-100且小于等于100。
 

Output
对于每组数据,首先需要输出单独一行”Case #?:”,其中问号处应填入当前的数据组数,组数从1开始计算。
每组测试数据输出一行,输出一个整数,代表根据最优的打法,你走到右上角时可以获得的最大金币数目。
 

Sample Input
23 41 -1 1 02 -2 4 23 5 1 -902 21 11 1
 

Sample Output
Case #1:18Case #2:4
 


题解转自:http://blog.csdn.net/lttree/article/details/26590299#comments

Source
2014年百度之星程序设计大赛 - 资格赛
 
题目:http://acm.hdu.edu.cn/showproblem.php?pid=4826


百度之星资格赛第四题,看样子很像搜索,其实不然,稍微分析一下就可以发现会TLE。。。

DP啊,最讨厌的题目类型之一。

于是开始慢慢推,

到一个点 i,j  可以从上走到这,从下走到这,从右走到这。

所以,但是推的时候绝对不能用最新更新的来推。

以提供的第一组测试数据为例:

1 -1  1  0

2 -2  4  2

3 5  -90

第一列,只能从上到下:

1

3

6

比较第二列第一行,只能从左面到这  就是 0

第二列第二行,从左到这 和 从上到这,最大值就是1

第二列第三行,从左到这 和 从上到这,最大值就是11

这是从上向下比较的,

接下来从下向上推:

第二列第三行,从左到这  就是11

第二列第二行,从左到这 和 从下到这,最大值就是9

第二列第一行,从左到这 和 从下到这,最大值就是8

最后,比较每一行的最大值,存到数组中

第二列第一行8

第二列第二行9

第二列第三行11


以此类推,整道题就解决了,

要注意一点,在推到的时候,从上到下和从下到上要分别算!


恩,此代码 耗时15MS  268K内存(C++)


[cpp] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. /**************************************** 
  2. ***************************************** 
  3. *        Author:Tree                    * 
  4. *From :http://blog.csdn.net/lttree      * 
  5. * Title : Labyrinth                     * 
  6. *Source: hdu 4826                       * 
  7. * Hint  : DP                            * 
  8. ***************************************** 
  9. ****************************************/  
  10. #include <stdio.h>  
  11. int Map[101][101],dp1[101],dp2[101];  
  12. int MAX(int a,int b)  
  13. {  
  14.     return a>b?a:b;  
  15. }  
  16. int main()  
  17. {  
  18.     int t,T,m,n,i,j;  
  19.     scanf("%d",&T);  
  20.     for(t=1;t<=T;++t)  
  21.     {  
  22.         scanf("%d%d",&m,&n);  
  23.   
  24.         for(i=1;i<=m;++i)  
  25.             for(j=1;j<=n;++j)  
  26.                 scanf("%d",&Map[i][j]);  
  27.   
  28.         // step1 算出第一列的dp数组  
  29.         for( i=2;i<=m;++i )  
  30.             Map[i][1]+=Map[i-1][1];  
  31.   
  32.         // 第二列开始向后算  
  33.         for( j=2;j<=n;++j )  
  34.         {  
  35.             // 相应初始化  
  36.             dp1[0]=dp2[0]=dp1[m+1]=dp2[m+1]=-999999;  
  37.             // 先从下向上算,存到dp1数组中  
  38.             for( i=m;i>=1;--i )  
  39.                 dp1[i]=MAX( dp1[i+1],Map[i][j-1])+Map[i][j];  
  40.             // 再从上向下算,存到dp2数组中  
  41.             for( i=1;i<=m;++i )  
  42.                 dp2[i]=MAX( dp2[i-1],Map[i][j-1])+Map[i][j];  
  43.             // 比较 两者,存最大的金钱数量  
  44.             for( i=1;i<=m;++i )  
  45.                 Map[i][j]=MAX( dp1[i],dp2[i] );  
  46.         }  
  47.         printf("Case #%d:\n%d\n",t,Map[1][n]);  
  48.     }  
  49.     return 0;  
  50. }  

1 0
原创粉丝点击