USACO4.3.1--Buy Low, Buy Lower

针对一个包含N个整数的序列,本篇博客探讨如何找出最长递减子序列的长度及其不同子序列的数量。通过动态规划算法求解,并考虑了数值相同情况下的特殊处理。

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

乐播投屏是一款简单好用、功能强大的专业投屏软件,支持手机投屏电视、手机投电脑、电脑投电视等多种投屏方式。 多端兼容与跨网投屏:支持手机、平板、电脑等多种设备之间的自由组合投屏,且无需连接 WiFi,通过跨屏技术打破网络限制,扫一扫即可投屏。 广泛的应用支持:支持 10000+APP 投屏,包括综合视频、网盘与浏览器、美韩剧、斗鱼、虎牙等直播平台,还能将央视、湖南卫视等各大卫视的直播内容一键投屏。 高清流畅投屏体验:腾讯独家智能音画调校技术,支持 4K 高清画质、240Hz 超高帧率,低延迟不卡顿,能为用户提供更高清、流畅的视觉享受。 会议办公功能强大:拥有全球唯一的 “超级投屏空间”,扫码即投,无需安装。支持多人共享投屏、远程协作批注,PPT、Excel、视频等文件都能流畅展示,还具备企业级安全加密,保障会议资料不泄露。 多人互动功能:支持多人投屏,邀请好友加入投屏互动,远程也可加入。同时具备一屏多显、语音互动功能,支持多人连麦,实时语音交流。 文件支持全面:支持 PPT、PDF、Word、Excel 等办公文件,以及视频、图片等多种类型文件的投屏,还支持网盘直投,无需下载和转格式。 特色功能丰富:投屏时可同步录制投屏画面,部分版本还支持通过触控屏或电视端外接鼠标反控电脑,以及在投屏过程中用画笔实时标注等功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值