ural 鹰蛋&51nod 1306

传送门:
http://www.51nod.com/onlineJudge/questionCode.html#problemId=1306&noticeId=139696

思路:这道经典题我很久之前就想把它做掉了,但因为太懒一直没做,它的思路很传统也不难想到,但却十分经典给人以启发,
我的傻逼想法和论文中的想法是一样的。。。下面我叙述一下 dp 优化的简单过程
算法一:
考虑直接 dp ,设 f[i][j] 为用 i 个蛋,j层楼需要多少次
转移的话就是枚举一个 k ,f[i][j]=min(max(f[i1][k],f[i1][jk1])+1)
复杂度: O(N2M

算法二:我们将 dp 看成一个函数,从以下几个角度思考:
1.优化状态(定义域)
2.优化转移(决策)
3.值域范围
4.函数单调性

考虑几个极端情况,例如当 m=1 时答案肯定是 n
m很大的时候我们的最优策略就是二分查找,那么也就是说状态中 m 的数量级在log,这样我们就完成了对状态的优化
至于转移,我们观察,画出其函数图像,是两个单调函数取 max 得到,很明显是单峰的,那么我们可以三分最优点。
那么进一步的,我们从差分和单调性的角度可以知道 f[i][j]f[i][j1]<=1 ,那么我们可以通过归纳法证明随着 j 的增大其决策是单调的,这样我们的转移变为了O(1)
那么我们将复杂度改进到了 O(nlogn)

算法三:
算法二已经没有任何优化的余地了。。。我们把它扔进垃圾桶
观察值域,注意到值域不会太大,当 m=2 时我们可以这样构造:
自底向下,从 n 开始从大到小枚举i,每次向上走 i 步试一下,如果碎了,就在块内查,设步数是step,这样步数是 step(step+1)2 ,很容易知道是 O(n) 级别的,当然我们也可以直接分块。。。这样同理可以知道一般情况步数是 O(nm) 的。
很明显,楼的层数,蛋的个数,步数两两之间都是单调的,值域又很小,我们可以 dp 答案。
f[i][j] 为步数为 i ,用j个蛋最大确定的楼层数
那么 f[i][j]=f[i1][j]+f[i1][j1]+1
复杂度: O(n3logn+Qlogn)
多么像杨辉三角啊。。。实际上根据这个我们可以推出 m 个蛋x步的有关 x 的多项式,你可以推一下发现m=2时为 x(x+1)2 ,这说明我们前面的构造竟然是最优解。。。但比较麻烦,我们可以直接暴力 dp 然后二分查找,注意 m=12 的时候要特殊处理一下,我 m=2 时边界设的有问题爆了好久。。。

代码:

#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#define M 66
#define N 70002
#define N0 2000002 
using namespace std;
typedef long long LL;
LL n,m,f[N][M],g[N0];
const LL inf2 = 1000000000000000000LL;
const LL inf = 1600000000LL;
void init(){
    memset(f,0,sizeof(f));
    for (int i = 1;i < N - 1; ++i) f[i][1] = i;
    for (int i = 2;i < M - 1; ++i)
      for (int j = 1;j < N - 1; ++j){
         f[j][i] = f[j - 1][i] + f[j - 1][i - 1] + 1;
         if (f[j][i] > inf2) f[j][i] = inf2 + 1; }
    for (LL i = 1;i < N0 - 1; ++i){
      g[i] = g[i - 1] + 1 + (i * (i - 1) >> 1);
      if (g[i] > inf2) g[i] = inf2 + 1; }
}

LL case_two(LL n){
    n <<= 1;
    LL mid,l = 0,r = min(n,(LL)inf);
    while (r - l > 1){
        mid = (r + l)>>1;
        if (mid * (mid + 1) < n) l = mid;
        else r = mid;
    }
    if (l * (l + 1) >= n&&l * (l - 1) < n) return l;
    return r;
}

LL case_three(LL n){
    LL l = 0,r = N0 - 2,mid;
    while (r - l > 1){
        mid = (r + l)>>1;
        if (g[mid] < n) l = mid;
        else r = mid;
    }
    if (g[l] >= n&&g[l - 1] < n) return l;
    return r;
}

LL case_normal(LL n,LL m){
    LL l = 0,r = N - 2,mid;
    while (r - l > 1){
        mid = (r + l)>>1;
        if (f[mid][m] < n) l = mid;
        else r = mid;
    }
    if (f[l][m] >= n&&f[l - 1][m] < n) return l;
    return r;
}

void DO_IT(){
    int T;
    scanf("%d",&T);
    while (T--){
        scanf("%lld%lld",&n,&m);
        if (m == 1) printf("%lld\n",n);
        if (m == 2) printf("%lld\n",case_two(n));
        if (m == 3) printf("%lld\n",case_three(n));
        if (m > 3) printf("%lld\n",case_normal(n,m));
    }
}

int main(){
    init();
    DO_IT();
    return 0;
}

总结:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值