poj1952

又是一道最长上升子序列的变形,让你求最长下降子序列的长度和个数,长度很简单,统计个数关键是不能重复,什么叫做重复呢,就是你找出来的所有情况,不可以存在2种序列完全一模一样,比如3 2 1 3 2 1这个问题答案 3 1,因为第一个 虽然有2个3 2 1出现,但是他们是重复的,只能算是一种。首先一个问题,假如现在不考虑重复,怎么求最长下降子序列的个数。对于这个问题,我首先想起来以前做过的一道关于最短路的条数的问题,有点类似,如果找到一个更优的状态,则赋值,如果找到了一个一样优的状态,则状态值加过去。先看下不考虑重复代码如下:

#include<stdio.h>
int a[5001];
int ans1,ans2;
int sum[5001],len[5001];
int main()
{
    int n;
    while(scanf("%d",&n)!=EOF)
    {
        int i,j;
        for(i=1;i<=n;i++)
            scanf("%d",a+i);
        ans1=ans2=0;
        for(i=1;i<=n;i++)
        {
            len[i]=1;
            sum[i]=1;
            for(j=i-1;j>=1;j--)
            {
                if(a[i]<a[j])
                {
                    if(len[i]==len[j]+1)
                        sum[i]+=sum[j];
                    else if(len[i]<len[j]+1)
                    {
                        len[i]=len[j]+1;
                        sum[i]=sum[j];
                    }
                }
            }
            if(ans1<len[i])
                ans1=len[i];
        }
        for(i=1;i<=n;i++)
        if(ans1==len[i])
        ans2+=sum[i];
        printf("%d %d\n",ans1,ans2);
    }
    return 0;
}
代码中len数组和sum数组分别表示的以第i个数为结尾的最长下降子序列的长度和个数,对于第i个数,如果我在前面找到了一个比它大的数a[j],那么也是分两种情况,第一,以j为结尾的最长下降子序列加上1能比当前以i为结尾的最长子序列更长,那么状态直接转移,相应的,要把个数赋值,因为当前找到了最优的,以i为结尾的最长下降子序列的个数一定等于以j为结尾的。第二,以j为结尾的最长下降子序列已经加上1和当前以i为结尾的最长子序列一样,那么有sum[i]+=sum[j],此时说明以i为结尾的最长下降子序列的个数一部分来源于以j为结尾的。最终的答案相信不难看懂。

但是上述代码不是本题的答案。

如何考虑重复呢?记得用O(nlogn)方法做最长上升子序列的时候,有一个重要的性质就是,假如我们找到了a[i]=a[j],i<j并且他们都可以作为a[k](k<i<j)状态转移选择(即a[k]<a[i]=a[j]),那么是选择a[i],而不是a[j],因为如果我们选了a[j],相当于假如i与j中间存在一个a[p]满足i,j的状态转移选择(即a[p]>a[i]=a[j]),那么选了j就相当于放弃了这个a[p]。所以在这里就产生了一个思想,如果存在相同的数,我只考虑前面的,ac代码如下:

#include<stdio.h>
int a[5001];
int ans1,ans2;
int sum[5001],len[5001];
int mark[5001];
int main()
{
    int n;
    while(scanf("%d",&n)!=EOF)
    {
        int i,j;
        for(i=1;i<=n;i++)
        {
            scanf("%d",a+i);
            mark[i]=1;
        }
        ans1=ans2=0;
        for(i=1;i<=n;i++)
        {
            len[i]=1;
            sum[i]=1;
            for(j=i-1;j>=1;j--)
            {
                if(a[i]<a[j]&&mark[j]==1)
                {
                    if(len[i]==len[j]+1)
                        sum[i]+=sum[j];
                    else if(len[i]<len[j]+1)
                    {
                        len[i]=len[j]+1;
                        sum[i]=sum[j];
                    }
                }
                else if(a[i]==a[j])
                {
                    if(len[i]==1)
                        mark[i]=0;
                    break;
                }
            }
            if(ans1<len[i])
                ans1=len[i];
        }
        for(i=1;i<=n;i++)
        if(ans1==len[i])
        ans2+=sum[i];
        printf("%d %d\n",ans1,ans2);
    }
    return 0;
}

就是在原来代码的基础上加上了这样的做法,处理第i个数时,如果我发现i的前面有个数a[j]==a[i],我就可以跳出j的循环了,因为j前面的数(a[1]到a[j-1])转移到i和转移到j是一样的,而j与i之间的数(a[j+1]到a[i-1])转移到i的状态我已经遍历过了,这里也说明了一点j的循环必须从i-1到1不能从小到大循环。如果j与i之间不存在大于a[i]的数,那么len[i]=1,此时a[i]这个数就是个“废数”,他的作用完全可以被a[j]取代,为了不让这个数影响它后面的数,我把它标记为0,并且每次往前找数的时候,只找标记为1的数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值