ural 鹰蛋&51nod 1306

来源:互联网 发布:mac限时免费大全 编辑:程序博客网 时间:2024/06/04 18:51

传送门:
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
原创粉丝点击