USACO4.3.1--Buy Low, Buy Lower

chunlvxiong的博客


题目描述:

  给出N(1≤N≤5000)个数,要求一个子序列是递减的,求出这个子序列的最大长度s,和长度为s的子序列的个数(注意,如果两个序列的数值一模一样它们算同一种序列)。

思考&分析:

  用a[i]表示第i个数。

  第一问求s应该较好解决,使用DP求解,用dp[i]表示i作为子序列末尾的最大长度,则方程为:

  dp[i]=max{dp[j]+1|1≤j<i且a[j]>a[i]}

  然后ans1=max{dp[i]|1≤i≤n},时间复杂度O(N^2)。

  第二问如果没有数值一样算同一种序列的限制,也比较好解决,同样使用DP求解,用f[i]表示i作为子序列末尾且该子序列长度最大的方案种数:

  dp[i]==1:f[i]=1

  dp[i]>1:f[i]=Σf[j](1≤j<i且a[j]>a[i]且dp[i]==dp[j]+1)

  ans2=Σf[i](dp[i]==ans1)

  问题在于所有数值一样算同一种序列,怎么避免这个问题呢?方法如下:

  如果存在a[x]==a[y]且dp[x]==dp[y](y<x),那么f[i]=Σf[j]中j的范围仅限于y+1..x-1,因为1..y-1中的方案末尾放a[j]或是a[i]都是一样的,为了避免重复计算,我们将其归于a[j],从而a[i]计算范围减少。

  对于dp[i]==1的,如果没有dp[j]==1(1≤j<i)且a[j]==a[i]的,那么f[i]=1,否则f[i]=0。

  仍然有ans2=Σf[i](dp[i]==ans1)。

  然而USACO出这个题的核心不在这儿……而是:由于N=5000,第二问的答案可能非常大,需要使用高精度。

  所以总的时间复杂度为O(N^2*高精度复杂度)-->N=5000时非常容易T,为了保险我压了四位,在USACO上跑了0.266s。

贴代码:

#include <bits/stdc++.h>
using namespace std;
int n,a[5005],dp[5005];
struct gjd{
    int num[25];
    int &operator [] (int p) { return num[p]; }
    void clear(){
        memset(num,0,sizeof(num));
    }
    void set(int x){
        clear();
        num[0]=0;
        while (x){
            num[++num[0]]=x%10;
            x/=10;
        }
        if (!num[0]) num[0]=1;
    }
    gjd operator +(gjd &b){
        gjd c; c.clear();
        c[0]=max(num[0],b[0]);
        for (int i=1;i<=c[0];i++){
            c[i]+=num[i]+b[i];
            c[i+1]+=c[i]/10000;
            c[i]%=10000;
        }
        while (c[c[0]+1]>0) c[0]++;
        return c;
    }
    void print(){
        printf("%d",num[num[0]]);
        for (int i=num[0]-1;i>=1;i--){
            if (num[i]<1000) putchar('0');
            if (num[i]<100) putchar('0');
            if (num[i]<10) putchar('0');
            printf("%d",num[i]);
        }
        puts("");
    }
}num[5005],res;
int main(){
    freopen("buylow.in","r",stdin);
    freopen("buylow.out","w",stdout);
    scanf("%d",&n);
    for (int i=1;i<=n;i++) scanf("%d",&a[i]);
    int ans=0;
    for (int i=1;i<=n;i++){
        dp[i]=1;
        for (int j=1;j<i;j++)
        if (a[i]<a[j])
            dp[i]=max(dp[i],dp[j]+1);
        ans=max(ans,dp[i]);
    }
    printf("%d ",ans);
    res.set(0);
    for (int i=1;i<=n;i++){
        if (dp[i]==1){
            num[i].set(1);
            for (int j=i-1;j>=1;j--)
            if (a[i]==a[j] && dp[j]==1){
                num[i].set(0);
                break;
            }
        }
        else{
            num[i].set(0);
            for (int j=i-1;j>=1;j--){
                if (a[i]==a[j] && dp[i]==dp[j])
                    break;
                if (a[i]<a[j] && dp[i]==dp[j]+1)
                    num[i]=num[i]+num[j];
            }
        }
        if (dp[i]==ans)
            res=res+num[i];
    }
    res.print();
    return 0;
}

转载于:https://www.cnblogs.com/chunlvxiong/p/7436214.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值