• HAOI2008 木棍分割 数据结构优化dp+二分答案


    很久之前打的题,现在补篇博客

    打滚动数组


    #E. 木棍分割

     Accepted

    100

    1712 ms

    1512 KiB

     

    2019-05-07 17:01:23

    Short 不打滚动数组

    #419. [HAOI2008]木棍分割

     Accepted

    100

    5219 ms

    100960 KiB

    2019-05-07 15:12:41

    木棍分割 题解

    木棍分割

    内存限制:128 MiB时间限制:3000 ms标准输入输出

    题目类型:传统评测方式:文本比较

    提交提交记录返回比赛

    题目描述

    n根木棍, i根木棍的长度为Li,n根木棍依次连结了一起, 总共有n-1个连接处. 现在允许你最多砍断m个连接处, 砍完后n根木棍被分成了很多段,要求满足总长度最大的一段长度最小, 并且输出有多少种砍的方法使得总长度最大的一段长度最小.

    并将结果mod 10007。。。

    输入格式

    输入文件第一行有2个数n,m.接下来n行每行一个正整数Li,表示第i根木棍的长度.n<=50000,0<=m<=min(n-1,1000),1<=Li<=1000.

    输出格式

    输出有2个数, 第一个数是总长度最大的一段的长度最小值, 第二个数是有多少种砍的方法使得满足条件.

    样例

    样例输入

    3 2                           

    1

    1

    10

    样例输出

    10 2

    数据范围与提示

    两种砍的方法: (1)(1)(10)(1 1)(10)

    二分答案+dp滚动数组

    第一步 二分答案求长度

    每次枚举木棍可以分成的值下界为所有木棍最小值上界为所有木棍最大值

    当前值可以分成的时候,比它小的值可能可以分成故让当前r=mid-1

    当前值不可以分成的时候比它大的值可能分成故让l=mid+1

    r>mid时得到最终答案

    然后find函数检验当前值是否可以分成

    具体操作:枚举!

    初始化add=0,指针=0,块数=0

    只需要扫一遍当add<=x的时候直接在add加上当前这个值;否则令分成的块数++ 并且令add值重新附成当前指针指向的木棍长度

    值得注意的一点是 当指针已经指向最后一个数的时候 add不为0 块数需要+1

    最后比较块数与m即可

    第二步 dp求方案数

    首先暴力枚举求方案数肯定会超时,很自然的想到统计方案需要用dp

    首先想要至少开二维 当前切割位置可能由任何满足之间木棍长度之和<=第一步求得的答案   的切割位置转移过来

    需要枚举每一根木棍(枚举当前切割的位置) ,每一个前一个切割的位置,和切割的次数

    可以看到是n2*m复杂度 还不如打一个暴力 同样是t

    dp暴力转移式$f[i][j]+=f[k][j-1](if(sum[i]-sum[k-1]<=maxn)$

    善良的出题人肯定不会让你AC

    你会得到0分的好成绩

     Time Limit Exceeded

    0

    30150 ms

    然后我们要考虑一些优化

    我们可以看到事实上一些位置是加重了的

    在每一个切割次数下要枚举一遍k 每次都枚举了k 但事实上每个木棍长是始终不变的

    每次枚举一遍就太区区了

    我们需要一个数据结构来快速查询每一段所代表的值并且维护一个before数组代表在其之前最长的一段<=第一步求的答案

    before可以暴力去求也可以用 lower_bound

    for(ll i=1;i<=n;i++)

    last[i]=lower_bound(sum+1,sum+i+1,sum[i]-cun)-sum;

    理解一下

    例如 9 3

     5 4 3 2 1 2 3 4 5

    这一组数据对应 before

     1 1 1 1 2 2 3 5 7

    cun=7

    sum 5 9 12 14 15 17 20 24 29

    对应下标 1 2  3  4  5   6   7  8  9

    last[1]=1 sum[1]-cun=-4

    last[2]=1 sum[2]-cun=0

    last[3]=1 sum[3]-cun=3

    last[4]=1 sum[4]-cun=5

    last[5]=2 sum[5]-cun=6

    last[6]=2 sum[6]-cun=8

    last[7]=3 sum[7]-cun=11

    last[8]=5 sum[8]-cun=15

    last[9]=7 sum[9]-cun=20

    lower_查到的下标 事实就是sum[i]-sum[now]<=cun 其实还是挺好理解的

    然后就是数据结构

    事实上 考虑到区间查值 我们可以用前缀和(当然树状数组不行 并没有单点修改仅仅是每次要查值而已 前缀和维护是o(1)  树状数组o(logn)

    然后dp就完了

    代码

    #include<bits/stdc++.h>
    #define re register
    #define i_ inline 
    #define huan cout<<endl
    #define ll int
    #define A 50010
    ll n,m,before[A],a[A],maxx=-1,last[A],sum[A],su[A],jilu;short f[50101][2];
    using namespace std;
    #define mod 10007
    inline ll read()
    {
        ll f=1,x=0;char ch=getchar();
        while(ch<'0'||ch>'9')
        {
            if(ch=='-') f=-1;
            ch=getchar();
        }
        while(ch>='0'&&ch<='9')
        {
            x=(x<<1)+(x<<3)+(ch^48);
            ch=getchar();
        }
        return f*x;
    }
    i_ bool find(ll x)
    {
        ll zhizhen=0,zan=0,add=0,dangqian;
        if(x<maxx)
            return false;
        while(zhizhen<=n)
        {
            zhizhen++;
            if(add+a[zhizhen]<=x)
            {
                add+=a[zhizhen];
            }
            else 
            {
                zan++;
                add=a[zhizhen];
                dangqian=zhizhen;
            }
        }
        if(add!=0)
            zan++;
        return (zan<=m?1:0);
    }
    i_ ll er()
    {
        ll l=0,r=sum[n],mid,ans;
        while(l<=r)
        {
            mid=(l+r)>>1;
            if(find(mid)==1)
                ans=mid,r=mid-1;
            else 
                l=mid+1;
        }
    //    printf("m=%lld
    ",jilu);
        return ans;
    }
    void tiaos(bool x)
    {
        printf("记录=%lld
    ",jilu);
        if(x)
        {
            for(ll i=1;i<=n;i++)
                printf("last[%lld]=%lld ",i,last[i]);
            cout<<endl;
            for(ll i=1;i<=n;i++,puts(""))
                for(ll j=0;j<=m;j++)
                    printf("f[%lld][%lld]=%lld  ",i,j,f[i][j]);
            return ;
        }
        else 
        {
            for(ll i=1;i<=n;i++,puts(""))
                for(ll j=0;j<=m;j++)
                    printf("f[%lld][%lld]=%lld  ",i,j,f[i][j]);
        }
    }
    int main()
    {    
    //    freopen("out.in","r",stdin);
    //    freopen("wrong.out","w",stdout);
        n=read(),m=read();
        m+=1;
        for(ll i=1;i<=n;i++)
        {
            a[i]=read();
            sum[i]=sum[i-1]+a[i];
            maxx=max(a[i],maxx);
        }
    
        ll cun=er(),zhe=1;    m=m-1;    ll uscao=0;
        for(ll i=1;i<=n;i++)
        {
            if(sum[i]>cun) break;
            else f[i][0]=1;
        }
        for(ll i=1;i<=n;i++)
            last[i]=lower_bound(sum+1,sum+i+1,sum[i]-cun)-sum;
        for(ll j=1;j<=m;j++)
        {
            for(ll i=1;i<=n;i++)
                su[i]=su[i-1]+f[i][j&1^1],f[i][j&1]=0;
            for(ll i=j+1;i<=n;i++)
                f[i][j&1]=(f[i][j&1]+su[i-1]-su[last[i]-1])%mod;
            uscao=(uscao+f[n][j&1])%mod;
        }
    
    //    tiaos(1);
        cout<<cun<<" "<<uscao;
    }
    View Code
    我已没有下降的余地
  • 相关阅读:
    QQ邮箱接收Gmail邮件的方法
    Python连接Mysql数据库——pymysql驱动
    nodejs pm2守护进程使用
    出现次数超过一半的数
    Good Hacker——模拟&&双向队列
    poj 2481 Cows——树状数组&&python
    Navicat15破解注册机详细教程-Navicat Keygen Patch v5.6.0
    宝塔面板6.X在Docker中安装宝塔面板5.9.1 – 我是不是太闲了?
    Vue全家桶(Vue-cli、Vue-route、vuex)
    基于.net core微服务(Consul、Ocelot、Docker、App.Metrics+InfluxDB+Grafana、Exceptionless、数据一致性、Jenkins)
  • 原文地址:https://www.cnblogs.com/znsbc-13/p/11121513.html
Copyright © 2020-2023  润新知