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

原题: 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


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值