• codevs 3327 选择数字


    3327 选择数字

     时间限制: 1 s
     空间限制: 256000 KB
    题目描述 Description

    给定一行n个非负整数a[1]..a[n]。现在你可以选择其中若干个数,但不能有超过k个连续的数字被选择。你的任务是使得选出的数字的和最大。

    输入描述 Input Description

    第一行两个整数n,k

    以下n行,每行一个整数表示a[i]。

    输出描述 Output Description

    输出一个值表示答案。

    样例输入 Sample Input

    5 2

    1

    2

    3

    4

    样例输出 Sample Output

    12

    数据范围及提示 Data Size & Hint

    对于20%的数据,n <= 10

    对于另外20%的数据, k = 1

    对于60%的数据,n <= 1000

    对于100%的数据,1 <= n <= 100000,1 <= k <= n,

                      0 <= 数字大小 <= 1,000,000,000

    第一步:dfs

    20分 TLE

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    int n,k;
    long long ans;
    long long a[100001];
    void dfs(int now,long long sum,int con)
    {
        if(now==n)
        {
            ans=max(ans,sum);
            return;
        }
        for(int i=now+1;i<=n;i++)
         {
             if(con<k) dfs(i,sum+a[i],con+1);
             dfs(i,sum,0);
         }
    }
    int main()
    {
        scanf("%d%d",&n,&k);
        for(int i=1;i<=n;i++) scanf("%d",&a[i]);
        dfs(0,0,0);
        printf("%lld",ans);
    }
    View Code

    这种写法好像不能改记忆化搜索

    第二步:朴素的DP

    唉,这一步也想不出来

    看的那篇题解里写着一句话:人傻就要多做题

    90分TLE 应该是数据弱。。。

    不能有超过连续的k个数字被选择,所以第i个状态可以由第i-k到i的状态更新而来

    定义f[i]表示选到第i个数字的最大和

    在i-k——i这段长为k+1的序列中,必须有一个断点

    枚举断点j

    状态转移方程:f[i]=max(f[j-1]+sum[i]-sum[j])   i-k<=j<=i

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    int n,k;
    long long a[100001],f[100001],sum[100001];
    int main()
    {
        scanf("%d%d",&n,&k);
        for(int i=1;i<=n;i++) scanf("%lld",&a[i]),sum[i]=sum[i-1]+a[i];
        for(int i=1;i<=k;i++) f[i]=f[i-1]+a[i]; 
        for(int i=k+1;i<=n;i++)
          for(int j=i-k;j<=i;j++)
             f[i]=max(f[i],f[j-1]+sum[i]-sum[j]);
        printf("%lld",f[n]); 
    }
    View Code

    开始做的时候 j只枚举到i-1,忽略了选i-k——i-1这段长为k的序列的情况

    第三步:

    单调队列优化DP

    观察状态转移方程,交换后两项顺序:f[i]=max(f[j-1]-sum[j]+sum[i])

    当i固定式,sum[i]固定,所以f[i]只与j有关

    所以可以用一个单调递减的队列维护f[j-1]-sum[j]的最大值,

    f[i]=队首(f[j-1]-sum[j]的最大值)+sum[i]

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    int n,k,head,tail;
    long long a[100001],f[100001],sum[100001],d[100001],q[100001];
    int main()
    {
        scanf("%d%d",&n,&k);
        for(int i=1;i<=n;i++) scanf("%lld",&a[i]),sum[i]=sum[i-1]+a[i];
        for(int i=0;i<=n;i++)
        {
            d[i]=f[i-1]-sum[i];
            while(head<tail&&q[head]<i-k) head++;
            while(head<tail&&d[i]>d[q[tail-1]]) tail--;
            q[tail++]=i;
            f[i]=d[q[head]]+sum[i];
        }
        printf("%lld",f[n]); 
    } 
    View Code
  • 相关阅读:
    pgsql 时间转时间戳
    java SHA-1加密
    java 输出json文件
    按照指定字符串截取文本
    java 科学计数法转换
    【原创】几个常用机场的对比
    【原创】Google Cloud服务器路由分析
    批量转换文件Unicode到中文
    博客搬家
    关于zookeeper
  • 原文地址:https://www.cnblogs.com/TheRoadToTheGold/p/6558033.html
Copyright © 2020-2023  润新知