您的位置:首页 > 其它

poj1952 BUY LOW, BUY LOWER(最长递减子序列及个数)

2017-09-06 17:51 399 查看
原题: http://poj.org/problem?id=1952

就是求最长递减子序列dp ,以及求不同的子序列的个数r ...思路和代码较长,请大牛见怪莫怪...
想法:我们需要在求最长递减子序列的过程中加一些额外的步骤,顺便把不同的子序列数目也算出来....明白下面两点其实就差不多了...详细过程看不看都行..

首先要明白:1. 对于相同的两个数ai,aj,ai在aj之前,一定有最长子序列dp[j]>=dp[i];且当最长子序列长度dp[i]==dp[j]时,  a2的不同子序列数目>=a1的不同子序列数目(后面简称情况数) ; 2.对于两个相同的数ai,aj,ai在aj前面,如果dp[i]==dp[j],且集合rs.size()>=2(即这个长度tmp前面已经有两个数字满足了),说明ai,aj之间一定隔了一些比ai,ai还要小的数字,所以情况数目kind[i]一定等于kind[j]。如果rs.size()==1,那么根据①,对于子序列长度相等,数字相同,后面出现的比前面出现的情况数多..直接r=kind[i]

下面是详细过程

所以我们需要一个额外的数组kind[]去记录各个位置的情况数,最长子序列长度为max,用一个集合set<int>rs,记录子序列长度为max的 那些数字,后面就知道为什么了

首先是求最长递减子序列的过程

外循环for(int i=1;i<n;i++)

          int tmp=1;

          ......

  内循环 for(int j=i-1;j>=0;j--)

            if(tmp<dp[j])...

              .......

我们需要在 if(tmp<dp[j]),多加一点修改:,我们需要求出每一个位置i的情况数目,先借用一个变量ans记录位置 i 的情况数

1.对于子序列长度相同tmp==dp[j]的情况: 因为第二层循环我们是从后往前遍历,根据定理①对应相同的数字,后面的出现的情况数目一定>=前面的情况数,所以如果这个数出现过,则不用理睬,如果没出现过就 把相应的情况数目kind[j]加进来。判断有没有出现过,我们可以借用set。

2.对于子序列长度不同tmp<dp[j]的情况:  直接选择较长的那一个,情况数目ans 等于那个位置的情况数目

外循环for(int i=1;i<n;i++)

          int tmp=1;

          int ans=0;

          set<int>s;//记录a[j]是否出现过

          ......

         for(int j=i-1;j>=0;j--)

            if(tmp==dp[j]){

                if(s.count(a[j])==0)  ...

                 else ...

            }

            else if(tmp<dp[j])  ans=kind[j]

求得tmp 和 kind[i]后,我们自然需要去跟max比较,求最长嘛

我门要明白的第二点 2.对于两个相同的数ai,aj,ai在aj前面,如果dp[i]==dp[j],且集合rs.size()>=2(即这个长度tmp前面已经有两个数字满足了),说明ai,aj之间一定隔了一些比ai,ai还要小的数字,所以情况数目kind[i]一定等于kind[j]。如果rs.size()==1,那么根据①,对于子序列长度相等,数字相同,后面出现的比前面出现的情况数多..直接r=kind[i]

if(tmp>max) ....直接更新 max=tmp ; r=kind[i] ; rs.insert(a[i])

else if(tmp==max) 判断 if (rs.count(a[i])==0 ) ...  else if (rs.size()==1)    ...

#include<cstdio>
#include<set>
using namespace std;
int main()
{
int n;
const int size=5100;
while(~scanf("%d",&n))
{
int a[size];
for(int i=0;i<n;i++)
{
scanf("%d",&a[i]);
}

//最长子序列为max
int max=1;//结果长度
int r=1;//不同的子序列数目
set<int>rs; //存放最长子序列长度为max的那些数字
rs.insert(a[0]);

int kind[size]={1};//记录不同位置的不同子序列数目
int dp[size]={1};//到i的最长序列长度
for(int i=1;i<n;i++)
{
int tmp=0;
int ans=1;
set<int>s2;
for(int j=i-1;j>=0;j--)//从后向前面遍历
{
if(a[i]<a[j])
{
if(tmp<dp[j])//如果小于,直接更新
{
s2.clear();
s2.insert(a[j]);
tmp=dp[j];

ans=kind[j];
}else if(tmp==dp[j])
{
if(s2.count(a[j])==0) //是否出现过
{
s2.insert(a[j]);
ans=ans+kind[j];
}
}
}
}
dp[i]=tmp+1;//记录最长子序列的长度
kind[i]=ans;//记录位置i的情况数目
if(max<dp[i])//如果大于max,直接更新
{
max=dp[i];
//更新最长
r=kind[i];
rs.clear();//清空
rs.insert(a[i]);//插入
}else if(max==dp[i])//如果等于
{
//先判是否出现过
if(rs.count(a[i])==0)//没有出现过直接加到r上面
{
rs.insert(a[i]);
r=r+kind[i];
}else if(rs.size()==1)//根据①如果出现过了,但是集合只有一个元素,那么根据定理①,直接更新r即可。根据②,rs.size()>=2不用理睬
{
r=kind[i];
}
}
}
printf("%d %d\n",max,r);
}
return 0;
} //ac
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: