• 【HAOI2008】木棍分割


    原题:

     n<=5e4,m<=min{n-1,1000},li<=1000

    最大的最小

    那必然是二分答案

    二分一个答案,检查的时候可以贪一个,对于某根木棍,如果它可以并入前一段,那么不并入前一段一定不会比并入前一段更优,这个易证

    这样就解决了子问题1

    求总长度最大的一段最小的方案数时,因为已经知道了最大值最小是多少,所以直接求所有木棍的长度都<=ans1的方案数

    虽然题目要求的是最大值恰好为ans1,但是最大值最小只能是ans1,所以不用担心把最大值小于ans1的方案统计进来

    然后容易得到一个二维dp

    f[i][j]表示直到第i个木棍,已经拼接了j段的方案数,s表示木棍长度的前缀和

    那么显然f[i][j]=∑f[k-1][j-1](s[i]-s[k-1]<=ans1)

    优化1:

    可以发现s[i]-s[k-1]具有单调性,那么k的取值是一个连续的区间,并且可以由二分快速得到

    优化2:

    既然k的取值是一个连续区间,那么求出f函数在第一维的前缀和g,就可以快速得到合式的值

    这样时间已经OK了,但是空间太大

    于是我就卡在这一步……

    思索片刻决定扫一眼题解,并豁然开朗,并且认为看题解是明智的选择

    可以发现,对于所有f[i][j]都是从f[k][j-1]转移而来,即第二维只与上一层有关

    而且两个维度相互独立

    那么可以调整dp顺序,先枚举第二维,然后对第二维做滚动数组

    易错点:

    1.f的初始状态为f[0][0]=1,但是注意,g数组初值不能只赋g[0][0]

    2.虽然这道题中f和g的转移都可以直接赋值,滚动数组不用清空,但是g的初值需要清空,否则会出现f[0][2]=1,使得答案错误

    3.交换了二分顺序之后不要把找k的范围的二分放在里边,否则时间不正确

    因为对于所有的i,k的范围都是一样的,与j无关,所以可以预处理

    代码:

     1 #include<iostream>
     2 #include<cstdio>
     3 using namespace std;
     4 const int mo=10007;
     5 int n,m,a[51000];
     6 int s[51000];
     7 int ans1=0,ans2=0;
     8 //int f[51000][1100];
     9 //int g[51000][1100];
    10 int f[51000][2];
    11 int g[51000][2];
    12 int t[51000];
    13 bool chck(int x){
    14     int bwl=0,cnt=1;
    15     for(int i=1;i<=n;++i){
    16         if(a[i]>x)  return false;
    17         if(bwl+a[i]>x){
    18             bwl=0;
    19             ++cnt;
    20         }
    21         bwl+=a[i];
    22     }
    23     return cnt<=m;
    24 }
    25 int bsa(){
    26     int l=1,r=s[n],md;
    27     while(l+1<r){
    28         md=(l+r)>>1;
    29         (chck(md) ? r : l)=md;
    30     }
    31     return chck(l) ? l : r;
    32 }
    33 int bsi(int x){
    34     int l=1,r=x,md;
    35     while(l+1<r){
    36         md=(l+r)>>1;
    37         (s[x]-s[md-1]<=ans1 ? r : l)=md;
    38     }
    39     return s[x]-s[l-1]<=ans1 ? l : r;
    40 }
    41 int main(){
    42     cin>>n>>m;
    43     ++m;
    44     for(int i=1;i<=n;++i)  scanf("%d",&a[i]);
    45     for(int i=1;i<=n;++i)  s[i]=a[i]+s[i-1];
    46     ans1=bsa();
    47     f[0][0]=1;
    48     for(int i=0;i<=n;++i)  g[i][0]=1;
    49     //注意g的初值不能只赋g[0][0]
    50     /*
    51     for(int i=1;i<=n;++i){
    52         int tmp=bsi(i);
    53         for(int j=1;j<=m && j<=i;++j){
    54             if(tmp>1){
    55                 f[i][j]=((f[i][j]+g[i-1][j-1]-g[tmp-2][j-1])%mo+mo)%mo;
    56             }
    57             else{
    58                 f[i][j]=((f[i][j]+g[i-1][j-1])%mo+mo)%mo;
    59             }
    60             g[i][j]=(g[i-1][j]+f[i][j])%mo;
    61         }
    62     }
    63     */
    64     for(int i=1;i<=n;++i)  t[i]=bsi(i);
    65     for(int j=1;j<=m;++j){
    66         if(j!=1)  g[0][j&1]=0;  //注意,当j!=0时g[0][j]应当被清空
    67         for(int i=1;i<=n;++i){
    68             int tmp=t[i];
    69             if(tmp>1){
    70                 f[i][j&1]=(g[i-1][(j&1)^1]-g[tmp-2][(j&1)^1]+mo)%mo;
    71             }
    72             else{
    73                 f[i][j&1]=g[i-1][(j&1)^1];
    74             }
    75             g[i][j&1]=(g[i-1][j&1]+f[i][j&1])%mo;
    76         }
    77         ans2=(ans2+f[n][j&1])%mo;
    78     }
    79     //for(int i=0;i<=m;++i)  ans2=(ans2+f[n][i])%mo;
    80     cout<<ans1<<" "<<ans2<<endl;
    81     //注意ans2不是g[n][m]
    82     return 0;
    83 }
    View Code
  • 相关阅读:
    PHP中遍历stdclass object
    php获取post中的json数据
    PHP的范围解析操作符(::)的涵义
    编程笔记之—sina&tqq api—MBApiClient 与 WeiboClient 冲突
    asp.net开发mysql注意事项
    vs2010 和 svn的结合运用,svn的安装
    asp.net 调用 mysql 存储过程 MySql.Data.MySqlClient.MySqlException: Unhandled type encountered
    asp.net 调用 mysql 分页存储过程,repeater使用mysql分页存储过程,mysql 存储过程的注意事项
    如何把其他项目中编写好的 自定义控件 放到自己的项目里面使用 (MultiListBox ASP.NET控件)
    jqPlot——基于jquery的图表绘制工具简单使用,jqPlot 在firefox 火狐浏览器里面,图表右侧的标题,变形的解决方法
  • 原文地址:https://www.cnblogs.com/cdcq/p/12661220.html
Copyright © 2020-2023  润新知