编号(长度)为状态的动态规划(LCS,LIS等)

时间:2022-09-10 08:18:26

 

这一系列的文章是一步步的按照Amber大牛的DP总结一步步的写各个分类的DP的记录。

 

之后做DP会更新增加这一系列的文章的内容的。

 

共性总结

 

  本类的状态是基础的基础,大部分的动态规划都要用到它,成为一个维。

 

  一般来说,有两种编号的状态:

 

  状态(i)表示前i个元素决策组成的一个状态。

 

  状态(i)表示用到了第i个元素,和其他在1到i-1间的元素,决策组成有的一个状态。

 

 

 

题库:

 

a)最长的不下降子序列(LIS)(有O(n^2),O(nlgn)两种)

 

以一元组(i)作为状态,表示第i个作为序列的最后一个点的时候的最长序列。于是很容易想到O(n2)得算法。但本题可合理组织状态,引入一个单调的辅助数组,利用单调性二分查找,优化到O(nlogn)。

 

 

 

O(n^2):

 

很显而易见的DP方程,

 

状态:dp[i]表示到第i个元素为结尾的最长不下降子序列的长度。

 

状态转移方程:dp[i] = max(dp[j])+ 1 (j <i&& num[i] <= num[j] );

 

 

 

 

 

O(n * lgn) :

 

状态:f[i]表示长度为i的最长不下降子序列最后一个元素的最大的值。f数组一定是单调的

 

状态转移方程:如果第i个元素小于等于于当前最长len的f[len],则f[++len] = num[i] ;

 

否则二分去找最靠后的f[]使得其值大于等于当前的值,更新其后面那个长度的f值后就可以了的。

 

 

 

 

 

题目:

 

拦截导弹(NOIP99 Advance 1)

 

http://acm.nuaa.edu.cn/acmhome/problemdetail.do?&method=showdetail&id=1005

 

 

 

这题之前做的是http://judge.noi.cn/problem?id=1084这个题目的,因为不知道数据范围,所以用的nlgn的算法。

 

这题第一个问题很明显的最长不下降子序列的。第二个问题,我之前想的是一直去找最长不下降子序列,然后记录最长的那个上的元素,直到没有被标记的就结束了的。写了后交了结果是40分,把我郁闷到了。正好红薯在边上,他说这个第二问有另外一种方法,就是利用Dilworth去求解最小链划分等于最长反链。相关证明在http://hi.baidu.com/lambda2fei/blog/item/c48f61a531f20bfb9152eec0.html

 

其实我理解就是,在一个偏序集里面,如果一个满足偏序集的一个最小的划分,等于与此偏序集相反的比较的那个偏序集的最长链。。。就是说:最长不下降子序列的最小划分等于最长上升子序列;最长不上升子序列的最小化分等于最长下降子序列等等。。。。如果想深入研究请参考那篇blog我是没看懂证明过程。

 

写完之后交了还是得了40分,把我郁闷到了。然后还有一个oier找我说我过了,问我咋做的,我回复说我才得到了40分。郁闷到了,就直接在南航的那个交了,结果1Y。。。囧。。。。

 

 

 

Beautiful People (sgu199)

 

http://acm.sgu.ru/problem.php?contest=0&problem=199

 

这题题意就是说给你N个人,分别有两个属性,s, b.如果两个人s1< s2 &&b1<b2 || s1>s2 &&b1>b2才能在一起,现在问你能有最多有多少个人可以再一起。然后输出最多的人数,和人的标号。n的范围10^5;

 

直接对第一维s排序(排序当s相等的时候,把b大的放在前面)然后就是一个最长上升子序列了的。因为n比较大,所以你不能n^2的去做,只能nlgn的去做,但是这个题目要输出路径,所以要想下那个nlgn的过程,然后那个路径如何去记录的。

 

nlgn的过程中,是以长度为下标的。是要不断的更新的,所以你在记录path的过程中,是要有另外的一个辅助数组去记录长度为i的此时是标号为几的index数组。然后在DP的过程中去更新他们。在输出的过程中,不能一直按照path去递归的去输出,要找到最长的那个最后一个元素是谁,然后跑一个长度len的循环去输出解。。

代码: 

 

 

 

 

 

Segment (ural 1078)

 

http://acm.timus.ru/problem.aspx?space=1&num=1078

 

这个题目就是给你n个线段有起始点终止点。让你求一个最大的子集,使得子集里面的线段一个能被另外一个完全包含。

 

按照第一位排序就是个简单的最长下降子序列的。

 

 

代码: 

 

 

 

 

 

 

LCS

 

poj 1080 :Human Gene Functions 

 

http://acm.pku.edu.cn/JudgeOnline/problem?id=1080

 

代码:

 

 

 

 

 

 

 

 

poj 1159 :Palindrome

 

http://acm.pku.edu.cn/JudgeOnline/problem?id=1159

 

 

代码: 

 

 

 

 

poj 1458 Common Subsequence

 

http://acm.pku.edu.cn/JudgeOnline/problem?id=1458

 

 

代码: 

 

 

 

 

poj 2192:Zipper

 

http://acm.pku.edu.cn/JudgeOnline/problem?id=2192

 

 

代码

以上是POJ上最LCS题目,可以拿来练手。

 

 

 

 

 

 

 

花店橱窗布置(IOI99)

 

poj : 1157 LITTLE SHOP OF FLOWERS 

 

http://acm.pku.edu.cn/JudgeOnline/problem?id=1157

 

#include<iostream>   

using namespace std;   

int main()   

{   

    int Map[110][110];   

    int dp[110][110];   

    int f,v,i,j,k;   

  scanf("%d%d",&f,&v);   

 

    for(i=1;i<=f;i++)   

     for(j=1;j<=v;j++)   

       scanf("%d",&Map[i][j]);   

   for(i=0;i<110;i++)   

     for(j=0;j<110;j++)   

       dp[i][j]=f*(-50);   

   for(i=1;i<=v;i++)   

     dp[1][i]=Map[1][i];   

    for(i=2;i<=f;i++)   

     for(j=i;j<=v;j++)   

     {   

                  for(k=i-1;k<j;k++)   

                  {   

                                  if(dp[i-1][k]+Map[i][j]>dp[i][j])   

                                    dp[i][j]=dp[i-1][k]+Map[i][j];   

                  }   

 

     }   

     k=dp[f][f];   

     for(i=f+1;i<=v;i++)   

         if(dp[f][i]>k)   

                    k=dp[f][i];   

    printf("%d/n",k);   

     system("pause");   

     return 0;   

}     

#include<iostream>

using namespace std;

int main()

{

    int Map[110][110];

    int dp[110][110];

    int f,v,i,j,k;

  scanf("%d%d",&f,&v);

 

    for(i=1;i<=f;i++)

     for(j=1;j<=v;j++)

       scanf("%d",&Map[i][j]);

   for(i=0;i<110;i++)

     for(j=0;j<110;j++)

       dp[i][j]=f*(-50);

   for(i=1;i<=v;i++)

     dp[1][i]=Map[1][i];

    for(i=2;i<=f;i++)

     for(j=i;j<=v;j++)

     {

                  for(k=i-1;k<j;k++)

                  {

                                  if(dp[i-1][k]+Map[i][j]>dp[i][j])

                                    dp[i][j]=dp[i-1][k]+Map[i][j];

                  }

 

     }

     k=dp[f][f];

     for(i=f+1;i<=v;i++)

         if(dp[f][i]>k)

                    k=dp[f][i];

    printf("%d/n",k);

     system("pause");

     return 0;

}   

 

 

 

 

 

 

总结:1:排序可以使得有些题转换为DP去做。

 

           2:DP的状态想好了一定要去验证最优子问题和无后性。