动态规划求 最长递增子序列
2013-12-14 20:04
302 查看
给出一段整数序列 , 要求出这个序列的最长递增子序列 。
方法一:
我们可以利用背包状态转移的思想 , 从后先前分析 , 我们先只考虑前 i-1 个元素中的最长递增子序列 , 当求前 i
个元素的最长递增序列时 , 我们只需要确定第 i 个元素可以放在前 i-1 个元素中哪些元素的后面 , 则 i
元素就能跟该元素构成一个新的序列。即:
k <= i-1 ;
xy[i] > xy[k] , 则 i 元素就能放在 k 元素的后面 ,
这样就构成了一个新的递增序列
代码:
int main()
{
int
xy[100010] , n;
int
dp[100010];
while(scanf("%lld" , &n) != EOF)
{
int i ,
j;
for(i = 1; i
<= n; i++)
cin>>xy[i];
//memset(dp
, 0 , sizeof(dp));
dp[1] =
1;
for(i = 2; i
<= n; i++)
{
dp[i] =
1;
for(j = 1; j
< i; j++)//顺序查找
{
if(xy[j]
< xy[i] && dp[i]
< (dp[j]+1))
dp[i] =
dp[j]+1;
}
}//时间复杂度为:n*n
int maxsum =
0;
for(i = 1; i
<= n; i++)
if(dp[i]
> maxsum) maxsum = dp[i];
cout<<maxsum<<endl;
}
return
0;
}
方法二:(dp + 二分查找)
对于方法一 , 我们可以分析出其时间复杂度为:n*n , 这复杂度太高了 , 所以我们要在改善这个算法。
在方法一中我们是在用顺序查找来查找xy[i]在0~i-1中的位置 , 因此我们可以在这里下手 , 把顺序查找该为二分查找 ,
则时间复杂度变为了:n*lgn;
这个方法的原理:
首先我们知道 , 对于每一个长度(递增序列中的元素数量)的递增序列 , 我们只需要记录该序列的最后的一个元素就行了 ,
我们可以用一个数组 c 来储存 ,则c[1]就储存只有一个元素的递增序列中的最大那个数(也就是最后面那个数) , c[2]
就储存只有2个元素的递增序列中的最大那个数.........
因此我们就得到了一个有序的数组 c , 那么当我们确定 xy[i] 这个元素的最长递增序列时 , 我们只需要在 c
中找到最小的大于xy[i]的元素的位置 , 这个位置就是xy[i]的最长递增序列。
所以 , 我们需要长期来维护数组c
代码:
#include
using namespace std;
long long int xy[100100];
long long int c[100100];
long long int find(long long int n , long long int x)
{
long long
int left = 1 , right = n ;
long long
int mid;
while(left
<= right)
{
mid =
(right+left)/2;
if(c[mid] < x) left =
mid + 1; // 这里要注意 , 因为是要求大于 x 的元素的位子 ,
所以这里不能加等于
else right =
mid - 1;
}
return
left;
}
int main()
{
long long
int n;
while(scanf("%lld" , &n) != EOF)
{
long long
int i , j;
for(i = 1; i
<= n; i++)
cin>>xy[i];
c[0] =
-1;
c[1] =
xy[1];
long long
int len = 1;
for(i = 2; i
<= n; i++)
{
j = find(len , xy[i]);
c[j] =
xy[i]; //维护数组c
if(j
> len) len = j;
}
cout<<len<<endl;
}
return
0;
}
方法一:
我们可以利用背包状态转移的思想 , 从后先前分析 , 我们先只考虑前 i-1 个元素中的最长递增子序列 , 当求前 i
个元素的最长递增序列时 , 我们只需要确定第 i 个元素可以放在前 i-1 个元素中哪些元素的后面 , 则 i
元素就能跟该元素构成一个新的序列。即:
k <= i-1 ;
xy[i] > xy[k] , 则 i 元素就能放在 k 元素的后面 ,
这样就构成了一个新的递增序列
代码:
int main()
{
int
xy[100010] , n;
int
dp[100010];
while(scanf("%lld" , &n) != EOF)
{
int i ,
j;
for(i = 1; i
<= n; i++)
cin>>xy[i];
//memset(dp
, 0 , sizeof(dp));
dp[1] =
1;
for(i = 2; i
<= n; i++)
{
dp[i] =
1;
for(j = 1; j
< i; j++)//顺序查找
{
if(xy[j]
< xy[i] && dp[i]
< (dp[j]+1))
dp[i] =
dp[j]+1;
}
}//时间复杂度为:n*n
int maxsum =
0;
for(i = 1; i
<= n; i++)
if(dp[i]
> maxsum) maxsum = dp[i];
cout<<maxsum<<endl;
}
return
0;
}
方法二:(dp + 二分查找)
对于方法一 , 我们可以分析出其时间复杂度为:n*n , 这复杂度太高了 , 所以我们要在改善这个算法。
在方法一中我们是在用顺序查找来查找xy[i]在0~i-1中的位置 , 因此我们可以在这里下手 , 把顺序查找该为二分查找 ,
则时间复杂度变为了:n*lgn;
这个方法的原理:
首先我们知道 , 对于每一个长度(递增序列中的元素数量)的递增序列 , 我们只需要记录该序列的最后的一个元素就行了 ,
我们可以用一个数组 c 来储存 ,则c[1]就储存只有一个元素的递增序列中的最大那个数(也就是最后面那个数) , c[2]
就储存只有2个元素的递增序列中的最大那个数.........
因此我们就得到了一个有序的数组 c , 那么当我们确定 xy[i] 这个元素的最长递增序列时 , 我们只需要在 c
中找到最小的大于xy[i]的元素的位置 , 这个位置就是xy[i]的最长递增序列。
所以 , 我们需要长期来维护数组c
代码:
#include
using namespace std;
long long int xy[100100];
long long int c[100100];
long long int find(long long int n , long long int x)
{
long long
int left = 1 , right = n ;
long long
int mid;
while(left
<= right)
{
mid =
(right+left)/2;
if(c[mid] < x) left =
mid + 1; // 这里要注意 , 因为是要求大于 x 的元素的位子 ,
所以这里不能加等于
else right =
mid - 1;
}
return
left;
}
int main()
{
long long
int n;
while(scanf("%lld" , &n) != EOF)
{
long long
int i , j;
for(i = 1; i
<= n; i++)
cin>>xy[i];
c[0] =
-1;
c[1] =
xy[1];
long long
int len = 1;
for(i = 2; i
<= n; i++)
{
j = find(len , xy[i]);
c[j] =
xy[i]; //维护数组c
if(j
> len) len = j;
}
cout<<len<<endl;
}
return
0;
}
相关文章推荐
- 01背包和完全背包比较容易忽略的地…
- Distant Galaxy poj 3141
- unipue() 函数的功能
- poj 2015 Permutation Code
- poj 1088 经典的记忆化搜索
- 中位数的应用
- poj 1724
- poj1511(第一次用数组模拟邻接表…
- poj 2226
- poj2728最优比率生成树
- poj 1797
- poj 1251
- 余弦:我的渗透利器
- 从数据库存储,文件结构谈到B树,散列
- poj 1364
- poj 1236
- poj 2186 tarjan算法
- poj 3352
- poj 1523 求图的割点
- uva 10972 边—双连通分量