poj1952

来源:互联网 发布:mysql 查看锁定的表 编辑:程序博客网 时间:2024/05/02 15:07

又是一道最长上升子序列的变形,让你求最长下降子序列的长度和个数,长度很简单,统计个数关键是不能重复,什么叫做重复呢,就是你找出来的所有情况,不可以存在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的数。

0 0
原创粉丝点击