DP 子序列问题

函数lower_bound()在first和last中的前闭后开区间进行二分查找,返回大于或等于val的第一个元素位置。如果所有元素都小于val,则返回last的位置
举例如下:
一个数组number序列为:4,10,11,30,69,70,96,100.设要插入数字3,9,111.pos为要插入的位置的下标

pos = lower_bound( number, number + 8, 3) - number,pos = 0.即number数组的下标为0的位置。
pos = lower_bound( number, number + 8, 9) - number, pos = 1,即number数组的下标为1的位置(即10所在的位置)。
pos = lower_bound( number, number + 8, 111) - number, pos = 8,即number数组的下标为8的位置(但下标上限为7,所以返回最后一个元素的下一个元素)。
所以,要记住:函数lower_bound()在first和last中的前闭后开区间进行二分查找,返回大于或等于val的第一个元素位置。如果所有元素都小于val,则返回last的位置,且last的位置是越界的!!~
返回查找元素的第一个可安插位置,也就是“元素值>=查找值”的第一个元素的位置

最长不下降子序列问题
nlogn的解法:
d[i]表示长度为i的序列的最末尾的值的最小值,比如,1,3,5序列和1,2,4序列,都是长为3的可是4比5小,则d[3]=4。
ans 表示最长的长度
先把d[0]读入,
for(int i=1;i<n;i++){
scanf("%d",&x);
if(x>=d[ans])d[++ans]=x;//这是最长不下降子序列
else{
id=lower_bound(d,d+ans,x)-d;
d[id]=x;
}
if(x>d[ans])d[++ans]=x;
else{
id=lower_bound(d,d+ans,x)-d;
if(d[id]!=x)d[id]=x;//这是最长上升序列
}


}
POJ 2533 Longest Ordered Subsequence 最长上升子序列 ×
很简单的模板题,主要是注意上升子序列,如果用lower_bound的话,注意要一开始就把相同的处理一下。

 1 #include<cstdio>

 2 #include<algorithm>

 3 //strictly increasing

 4 using namespace std;  5 const int maxn=10005;  6 int n;  7 int d[maxn];  8 int ans;  9 int x; 10 

11 int main(){ 12     int id; 13     while(scanf("%d",&n)!=EOF){ 14 

15         ans=0; 16         scanf("%d",&d[0]); 17         for(int i=1;i<n;i++){ 18             scanf("%d",&x); 19             if(x>d[ans]){ 20                 ans++;d[ans]=x; 21             }else{ 22                 id=lower_bound(d,d+ans,x)-d; 23                 if(d[id]!=x) 24                     d[id]=x; 25  } 26  } 27         printf("%d\n",ans+1); 28  } 29 

30     return 0; 31 }
View Code

 


POJ1631 Bridging Signal 关键是建模,题意:线路连接很乱,而给出的要求就是保证最多的线,可是这些线都不能交叉,可以在左边或者右边的节点会合。Input:在某组测试数据中的第i个数x表示左边的第i个和右边的第x个有链接,而且保证左边的每一个都连接了右边的某个。output:输出能保留的最多的连接线。
分析,第i个的保留与否决定于前一个的状态,如果i-1的连接点比i的要小或者相等,那么则不会又交叉。则,第i个值和前面的值组成了链,而保留最多个,则就是这链最长。即,最长不下降子序列

 1 #include<cstdio>

 2 #include<algorithm>

 3 #include<cstring>

 4 using namespace std;  5 const int maxn=40005;  6 int n;  7 int d[maxn];  8 int ans;  9 int main(){ 10     int t; 11     scanf("%d",&t); 12     while(t--){ 13         scanf("%d",&n); 14         int x; 15         ans=0;//here is a wa

16         int id; 17         memset(d,0,sizeof(d)); 18         scanf("%d",&d[0]); 19         for(int i=1;i<n;i++){ 20             scanf("%d",&x); 21             if(x>=d[ans]){ 22                 d[++ans]=x; 23             }else{ 24                id=lower_bound(d,d+ans,x)-d; 25                d[id]=x; 26  } 27  } 28         printf("%d\n",ans+1); 29 

30  } 31     return 0; 32 }
View Code

 


POJ3903 StockExchange 最长上升子序列模板题 ×

 1 #include<cstdio>

 2 #include<algorithm>

 3 const int maxn=100005;  4 using namespace std;  5 

 6 int n;  7 int d[maxn];  8 int ans;  9 int main(){ 10     while(scanf("%d",&n)!=EOF){ 11         ans =0; 12         int x,id; 13         scanf("%d",&d[0]); 14         for(int i=1;i<n;i++){ 15             scanf("%d",&x); 16             if(x>d[ans])d[++ans]=x; 17             else{ 18                 id=lower_bound(d,d+ans,x)-d; 19                 if(d[id]!=x)d[id]=x; 20  } 21  } 22         printf("%d\n",ans+1); 23 

24  } 25     return 0; 26 }
View Code

 

最长下降子序列:POJ1952 Warning
buy low,buy lower.
题意:给出序列,求出最长下降子序列的长度和数量,这个数量的统计规则如下:这两个子序列的值是一样的,尽管序号可能不一样,此时记为一种情况,例如,数据 6 4 3 4 1 3 1 正确结果是3 1两个431是一样的。除此情况以外的所有的两个子序列的对比情况都记为两个。
要记录子序列的数量了,那么普通的那种nlogn的解法就出现了问题,因为每次的覆盖,很难得出一种记录方法,
题目要求找最长下降子序列,dp[i]表示以num[i]为最后一个的最长下降子序列长度,则dp[i]=max(dp[j]+1)其中要求num[j]>num[i]。当然这个题目还要求最长个数的总数,所以要再用一个数组记录,如果dp[j]和dp[k]对与num[i]结尾是相同长度的,那么mark[i]要将mark[j]和mark[k]加起来。注意,如果出现num[i]==num[j]&&dp[i]==dp[j]那么只能保留一个,因为求答案是要将情况加起来,而这时显然是同一种情况。

 1 /*dp lds  2 */

 3 #include <cstdio>

 4 #include <cstring>

 5 #include <algorithm>

 6 using namespace std;  7 const int maxn=5005;  8 int n;  9 int data[maxn],dp[maxn],opt[maxn]; 10 void solve(){ 11     memset(opt,0,sizeof(opt)); 12     //opt[i]表示选用第i个时最长序列的长度 13     //最长下降子序列:

14     for(int i=1;i<=n;i++){ 15         for(int j=1;j<i;j++){ 16             if(data[j]>data[i]){ 17                 opt[i]=max(opt[i],opt[j]+1); 18  } 19  } 20  } 21     //更新方案数 22     //dp[i]表示opt[j] opt[k]以data[i]结尾的最长下降子序列长度相同的方案数

23     for(int i=1;i<=n;i++)dp[i]=opt[i]==0?1:0;//如果data[i]只能是一个序列的头,则dp[i]=1;

24     for(int i=1;i<=n;i++){ 25         for(int j=1;j<i;j++){ 26             if(data[j]>data[i] && opt[j]==opt[i]-1){//如果j和i可以构成前后的链关系,则将他们的方案数加起来。

27                 dp[i]+=dp[j]; 28  } 29 /*WA点:数据 6 4 3 4 1 3 1 正确结果是3 1 30 如果发现前边与自己相同的而且最大长度也相同,则把前边那个的最大长度的数目置为0, 31 因为后边的那个一定能覆盖前边那个的所有情况的 32 */

33             if(data[j]==data[i] && opt[j]==opt[i]){ 34                 dp[j]=0; 35  } 36  } 37  } 38     int maxlen=-1,maxways=0; 39     for(int i=1;i<=n;i++){ 40         if(opt[i]==maxlen){ 41             maxways+=dp[i]; 42  } 43         if(opt[i]>maxlen){ 44             maxways=dp[i]; 45             maxlen=opt[i]; 46  } 47  } 48     printf("%d %d",maxlen+1,maxways); 49 } 50 int main(){ 51     scanf("%d",&n); 52     for(int i=1;i<=n;i++){ 53         scanf("%d",&data[i]); 54  } 55  solve(); 56 }
View Code

 

HDU 1087 有陷阱 最大上升子序列

 1 #include<cstdio>

 2 #include<cstring>

 3 #include<algorithm>

 4 using namespace std;  5 const int maxn=1005;  6 int n;  7 //opt[i]:end by data[i] lis's length dp[i]:lis[i]'s sum

 8 int data[maxn],opt[maxn],dp[maxn];  9 

10 int main(){ 11     while(scanf("%d",&n)&&n){ 12         for(int i=0;i<n;i++){ 13             scanf("%d",&data[i]); 14  } 15         memset(opt,0,sizeof(opt)); 16         memset(dp,0,sizeof(dp)); 17         int maxj; 18         //edge

19         for(int i=0;i<n;i++)dp[i]=data[i]; 20         for(int i=1;i<n;i++){ 21             maxj=-1; 22             int maxtemp=data[i]; 23             for(int j=0;j<i;j++){ 24                 if(data[j]<data[i]&&dp[j]+data[i]>maxtemp){ 25                     //wa;It's not about the longest but the biggest

26                     opt[i]=opt[j]+1; 27                     maxj=j; 28                     maxtemp=dp[j]+data[i]; 29  } 30  } 31             if(maxj!=-1) 32                 dp[i]=maxtemp; 33  } 34         maxj=0; 35         for(int i=0;i<n;i++) 36             if(dp[i]>maxj) 37                 maxj=dp[i]; 38         printf("%d\n",maxj); 39 

40  } 41 

42 

43     return 0; 44 }
View Code

题目要求找一条路线,使得这个路线上的值的和最大,路线是在数列中向右挑选,不能回头,上升。注意是要挑选值最大,而不是最长。
这里顺便说下朴素的n^2的解决LIS问题的方法,对于第i个数,以它结尾的子序列的长度,取决于它前面所有的数中,比它小的而且最长的。
data[i]:第i个数;opt[i]以data[i]为最后一个数能构成的最长的上升子序列的长度;
for(int i=0;i<n;i++)
for(int j=0;j<i;j++){
if(data[j]<data[i] && opt[j]+1>opt[i]){
opt[i]=opt[j]+1;
}
}
这是基础核心,在明确了这题是最大上升子序列之后,开始修改基础代码,首先要增加dp[i]表示以data[i]结尾的最大上升子序列的和,而判断条件 opt[j]+1>opt[i]也应该改为dp[j]+data[i]>maxtemp,这里的maxtemp表示在循环过程中能找到的最大的值。最后赋给dp[i]即可。那么在这个寻找过程中,必然要添加变量来记录序号,
for(int i=1;i<n;i++){
maxj=-1;
int maxtemp=data[i];
for(int j=0;j<i;j++){
if(data[j]<data[i]&&dp[j]+data[i]>maxtemp){
//wa;It's not about the longest but the biggest
opt[i]=opt[j]+1;
maxj=j;
maxtemp=dp[j]+data[i];
}
}
if(maxj!=-1)
dp[i]=maxtemp;
}

注意dp[i]初始化
想要更详细的请戳http://blog.csdn.net/waitfor_/article/details/7236623

 

HDU3998最长上升子序列及其数量 ×× 这题主要是数据比较若,主流解法实际上是网络流的东西。可以用n*log(n)的做法求出最长上升子序列,然后删除原数组中的这些数,再求最长上升子序列(如果长度减小,则直接退出)。

http://www.cnblogs.com/wally/archive/2013/05/05/3060572.html

http://www.cnblogs.com/Anker/archive/2013/03/11/2954050.html
还有O(nlogn)的解法,不过是不稳定的,
LCS -> LIS:把序列排序后与原序列找最长公共子序列
参考的总结:
http://www.cnblogs.com/celia01/archive/2012/07/27/2611043.html


hdu1423 最长上升公共子序列 ×× 好题,既上升,又公共

 1 #include<cstdio>

 2 #include<algorithm>

 3 #include<cstring>

 4 using namespace std;  5 const int maxn=502;  6 int lena,lenb;  7 int a[maxn],b[maxn];  8 int dp[maxn];  9 int lcis(){ 10     dp[0]=-1; 11     for(int i=0;i<lena;i++){ 12         int p=0; 13         for(int j=0;j<lenb;j++){ 14             if(b[j]<a[i]&&dp[j]>dp[p]) 15                 p=j; 16             if(b[j]==a[i]) 17                 dp[j]=(dp[p]>=0?dp[p]:0)+1; 18  } 19  } 20     int maxt=0; 21     for(int i=0;i<lenb;i++){ 22         maxt=max(maxt,dp[i]); 23  } 24     return maxt; 25 } 26 int main(){ 27     int t; 28     scanf("%d",&t); 29     while(t--){ 30         scanf("%d",&lena); 31         for(int i=0;i<lena;i++)scanf("%d",&a[i]); 32         scanf("%d",&lenb); 33         for(int i=0;i<lenb;i++)scanf("%d",&b[i]); 34         memset(dp,0,sizeof(dp)); 35         printf("%d\n",lcis()); 36         if(t)printf("\n"); 37  } 38 

39     return 0; 40 } 41 /* better to understand 42 #include<iostream> 43 #include<cstdio> 44 #include<cstring> 45 #include<algorithm> 46 using namespace std; 47 const int MAXN=550; 48 int dp[MAXN][MAXN]; 49 int num1[MAXN]; 50 int num2[MAXN]; 51 int n1,n2; 52 //dp[i][j]表示num1[]从1-i,num2[]1-j的最长公共子序列的长度 53 

54 

55 int main(){ 56  int _case,t=0; 57  scanf("%d",&_case); 58  while(_case--){ 59  memset(num1,0,sizeof(num1)); 60  memset(num2,0,sizeof(num2)); 61  if(t++)puts(""); 62  scanf("%d",&n1); 63  for(int i=1;i<=n1;i++)scanf("%d",&num1[i]); 64  scanf("%d",&n2); 65  for(int i=1;i<=n2;i++)scanf("%d",&num2[i]); 66  memset(dp,0,sizeof(dp)); 67  for(int i=1;i<=n1;i++){ 68  int ans=0; 69  for(int j=1;j<=n2;j++){ 70  dp[i][j]=dp[i-1][j]; 71  if(num2[j]<num1[i]&&ans<dp[i-1][j])ans=dp[i-1][j];//求出1-i,j中的最长公共子序列 72  if(num1[i]==num2[j]){ 73  dp[i][j]=ans+1; 74  } 75  } 76  } 77  int ans=0; 78  for(int i=1;i<=n2;i++)ans=max(ans,dp[n1][i]); 79  printf("%d\n",ans); 80  } 81  return 0; 82 } 83 */
View Code

dp[i][j]表示a从1-i,b的1-j的最长公共上身子序列的长度。因此每一个循环都要保存前一个的状态,对于每个a[i],然后都去找在b中的比它小的而且在已经构成的子序列中最长的上升子序列的尾部。这里可以通过一些样例将dp数组输出来看看。如果相同,则增长子序列的len。注释中的那个解法比较容易理解
HDU1160FatMouse'sSpeed 最长上升(下降)子序列 **很巧妙的题意

 1 #include<cstdio>

 2 #include<cstring>

 3 #include<algorithm>

 4 #include<stack>

 5 using namespace std;  6 struct node{  7     int id,weight,speed;  8     const bool operator<(struct node d)const{  9         return weight<d.weight; 10  } 11 }str[1009]; 12 int f[1009];//(end by i'th )'longest length

13 stack<struct node>s; 14 int main(){ 15     memset(f,0,sizeof(f)); 16     int a,b,num=0,i,j; 17     struct node temp; 18     while(scanf("%d%d",&a,&b)!=EOF){ 19         str[num].id=num+1; 20         str[num].weight=a; 21         str[num].speed=b; 22         num++; 23  } 24     int maxa;int mm=-1,k; 25     sort(str,str+num); 26     f[0]=1; 27     for(i=1;i<num;i++){ 28         maxa=-1; 29         for(j=i-1;j>=0;j--){ 30             if(str[i].speed<str[j].speed&&str[i].weight>str[j].weight&&maxa<f[j]) 31                 maxa=f[j]; 32 

33  } 34     if(maxa==-1)f[i]=1; 35     else f[i]=maxa+1; 36     if(mm<=f[i]){mm=f[i],k=i;} 37  } 38  s.push(str[k]); 39     j=f[k]; 40     int l=k; 41     for(i=l;i>=0;i--){ 42         if(f[i]==f[k]-1&&s.top().speed<str[i].speed&&s.top().weight>str[i].weight){ 43  s.push(str[i]); 44             k=i; 45  } 46     printf("%d\n",s.size()); 47  } 48 

49     while(!s.empty()){ 50         printf("%d\n",s.top().id); 51  s.pop(); 52  } 53     return 0; 54 }
View Code

要保留踪迹。可以根据排序,使得其中一个已经排序。然后对另一列进行LDS处理。一列上升,一列下降。因为需要输出原序,所以要记录好原序号,两个输入值,所以用node。f[i]表示以第i个数据结尾的最长下降(按照speed)的子序列的长度。剩下的就是朴素的O(n^2)的求解算法。记录序号可以采用两种办法:一种是在比较的时候记录好序号,然后加入到node里面;另一种就是通过得到的值,重新扫描,上一个值和当前值一定符合三个条件(见代码),得到序列。


最长公共子序列http://blog.chinaunix.net/uid-26548237-id-3374211.html
HDU1159 Common Subsequence LCS× 模板题

HDU1513 Palindrome 回文,要求最少的插入个数,可想到原文和回文做LCS,而数据规模是5000×5000肯定会TLE和MLE,那么可使用滚动数组。

 1 #include <cstdio>

 2 #include <iostream>

 3 #include<cstring>

 4 #include <algorithm>

 5 using namespace std;  6 int main(){  7     int N;  8     char a[2][5555];  9     int dp[2][5555]; 10     while(scanf("%d",&N)!=EOF){ 11         scanf("%s",a[0]); 12         for(int i=0;a[0][i];i++){ 13             a[1][N-i-1]=a[0][i]; 14  } 15         a[1][N]='/0';                                                                   //a[1]是a[0]的反串

16         memset(dp,0,sizeof(dp)); 17         int pre=0; 18         for(int i=1;i<=N;i++){ 19             for(int j=1;j<=N;j++){ 20                 if(a[0][i-1]==a[1][j-1]){                                              //1-pre表示现在的,pre表示之前的,s1[i]==s2[i],那么现在的就等于之前的+1

21                     dp[1-pre][j]=dp[pre][j-1]+1; 22  } 23                 else{ 24                     dp[1-pre][j]=max(dp[pre][j],dp[1-pre][j-1]);       //不相等则由之前的j或者现在的j-1继承过来

25  } 26  } 27             pre^=1; 28  } 29         printf("%d\n",N-dp[pre][N]); 30  } 31     return 0; 32 }
View Code

HDU1238Substrings 最长公共子串问题,暴搜即可,不过,最好用kmp来优化字符串的比较。=poj1226好题string的一些函数

 

 1 #include<cstdio>

 2 #include<cstring>

 3 #include<algorithm>

 4 using namespace std;

 5 const int maxn=103;

 6 const int INF=0x3f3f3f3f;

 7 char pstr[maxn],pstr2[maxn];

 8 char s[maxn][maxn];

 9 int len[maxn],next[maxn],next1[maxn];

10 int n,plen;

11 void getnext(char pstr[],int * next){//pattern string

12     int i=0;

13     next[i]=-1;

14     int j=-1;

15     while(i<plen-1){

16         if(j==-1||pstr[i]==pstr[j]){

17             ++i;++j;

18             if(pstr[i]!=pstr[j])next[i]=j;

19             else next[i]=next[j];

20         }else

21             j=next[j];

22     }

23 }

24 

25 int kmp(char  p[],int *next,int id){//target string

26     int i=0,j=0;

27     while(j<plen && i<len[id]){

28         if(j==-1 ||p[j]==s[id][i]){++i;++j;}

29         else j=next[j];

30     }

31     if(j>=plen)

32 //        return i-plen;

33         return 1;

34     else return -1;

35 }

36 int main(){

37     int t;

38     int minlen=INF,mini=0;

39     scanf("%d",&t);

40     while(t--){

41         bool isok=false;

42         scanf("%d",&n);

43         for(int i=0;i<n;i++){

44              scanf("%s",s[i]);

45              len[i]=strlen(s[i]);

46              if(minlen<len[i]){minlen=len[i];mini=i;};

47         }

48         for(int i=len[mini];i>=1;i--){

49             for(int j=0;i+j<=len[mini];j++){

50                 for(int x=0;x<=i;x++){//here you can draw a graph to understand better

51                     pstr[x]=s[mini][j+x];

52                     pstr2[x]=s[mini][j+i-x-1];

53                 }

54                 pstr[i]='\0';

55                 pstr2[i]='\0';

56                 plen=i;

57 

58                 getnext(pstr,next);

59                 getnext(pstr2,next1);

60 //                printf("%s%s\n",pstr,pstr2);

61 //                for(int ii=0;ii<len[mini];ii++)printf("%d ",next[ii]);

62 //                printf("\n");

63 //                for(int ii=0;ii<len[mini];ii++)printf("%d ",next1[ii]);

64 //                printf("\n");

65                 int k=1;

66                 for(int x=0;x<n;x++){

67                     if(x==mini)continue;

68 

69 //                    int x1=kmp(pstr,next,x),x2=kmp(pstr2,next1,x);

70 //                     printf("x1%d   x2%d\n",x1,x2);

71                     if(kmp(pstr,next,x)>0 || kmp(pstr2,next1,x)>0)k++;

72                     //negative numbers is true!!!!WARN

73                     else break;

74                 }

75                 if(k==n){

76                     isok=true;

77                     printf("%d\n",i);

78                     break;

79                 }

80             }

81             if(isok)break;

82 

83         }

84         if(!isok)printf("0\n");

85     }

86 

87 

88 

89     return 0;

90 }
View Code

 

 

 

 这个题,后续上比较好的代码。

 

系列题目:

POJ 2533 Longest Ordered Subsequence 最长上升子序列 ×
POJ1631 Bridging Signal 最长上升子序列模板题× =HDU1950 ~=HDU1025
POJ 1631 3903 1952
POJ 2533 Longest Ordered Subsequence 最长上升子序列 ×
POJ3903 StockExchange 最长上升子序列模板题 ×
HDU1087SuperJumping 最大上升子序列 ×
hdu1423 最长上升公共子序列 ×× 好题
HDU1159 Common Subsequence LCS× 模板题
HDU1513 Palindrome LCS+滚动数组 ××
HDU1238Substrings LCS+KMP 或者暴搜也可 ×××=POJ1226

 

有关KMP的可以参考此人的整理

http://www.cnblogs.com/chenxiwenruo/p/3546457.html

你可能感兴趣的:(dp)