传送门:
http://www.51nod.com/onlineJudge/questionCode.html#problemId=1306¬iceId=139696
思路:这道经典题我很久之前就想把它做掉了,但因为太懒一直没做,它的思路很传统也不难想到,但却十分经典给人以启发,
我的傻逼想法和论文中的想法是一样的。。。下面我叙述一下
dp
优化的简单过程
算法一:
考虑直接
dp
,设
f[i][j]
为用
i
个蛋,
转移的话就是枚举一个
k
,
复杂度:
O(N2M)
算法二:我们将
dp
看成一个函数,从以下几个角度思考:
1.优化状态(定义域)
2.优化转移(决策)
3.值域范围
4.函数单调性
考虑几个极端情况,例如当
m=1
时答案肯定是
n
当
至于转移,我们观察,画出其函数图像,是两个单调函数取
max
得到,很明显是单峰的,那么我们可以三分最优点。
那么进一步的,我们从差分和单调性的角度可以知道
f[i][j]−f[i][j−1]<=1
,那么我们可以通过归纳法证明随着
j
的增大其决策是单调的,这样我们的转移变为了
那么我们将复杂度改进到了
O(nlogn)
算法三:
算法二已经没有任何优化的余地了。。。我们把它扔进垃圾桶
观察值域,注意到值域不会太大,当
m=2
时我们可以这样构造:
自底向下,从
n
开始从大到小枚举
很明显,楼的层数,蛋的个数,步数两两之间都是单调的,值域又很小,我们可以
dp
答案。
设
f[i][j]
为步数为
i
,用
那么
f[i][j]=f[i−1][j]+f[i−1][j−1]+1
复杂度:
O(n√3logn+Qlogn)
多么像杨辉三角啊。。。实际上根据这个我们可以推出
m
个蛋
代码:
#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;
}
总结: