【动态规划】【换维】扔鸡蛋游戏
这是一道在《信息学奥赛一本通》上的经典题目。题目描述如下:
有 \(k\) 个一模一样的鸡蛋,楼的高度为 \(n\) ,定义鸡蛋的硬度为 \(x\) ,当且仅当将鸡蛋从 \(x\) 楼扔下不会碎,从 \(x + 1\) 楼扔下会碎,求最坏情况下求出鸡蛋硬度的最小步数。
\(k \leq 10,n \leq 100\) 。保证硬度在 \([0,n]\) 之间。
考虑假设只有两个蛋,最小步数是 \(t\) ,我们的策略是怎样的:
我们一定是分块来扔,考虑如果第一次碎了,那么硬度一定小于第一次扔的高度,此时我们还有 \(t - 1\) 次,可以确定 \(t - 1\) 个位置(扔 \(1\) 到 \(t - 1\) ,如果 \(1\) 碎了就是 \(0\)),所以第一次的高度选为 \(t\) 。后面同理,只不过每次都比上一次少了 \(1\) 。最后能确定的高度上限是 \(\frac {t(t + 1)}2\) 。
接下来假设我们有 \(3\) 个蛋,假设第一次高度为 \(x\) 。如果碎了,我们就要用 \(2\) 个蛋和 \(t - 1\) 次来确定剩下的高度,假设 \(f_{i,j}\) 为 \(i\) 个蛋 \(j\) 次能达到的高度。这个高度 \(x\) 就等于 \(f_{2,t - 1} + 1\) 。如果没碎,我们就要用 \(3\) 个蛋和 \(t - 1\) 次确定上面的高度,所以是 \(f_{3,t - 1}\) 。
综上所述, \(dp\) 式子已经很完全了,就是:
\[f_{i,j} = f_{i - 1,j - 1} + 1 + f_{i,j - 1} \]直接做即可,这道题目难以通过枚举高度和鸡蛋个数来算出次数,所以我们考虑换一维,用个数和次数推出最高高度,再通过二分回答询问,是一种很巧妙的思路,时间复杂度 \(\Theta(kn)\) 。
当然还有基于枚举高度的 \(\Theta(n^2k)\) 的做法,设 \(f_{i,j}\) 为硬度在 \([0,i]\) 之间,还有 \(j\) 个鸡蛋时的最小次数,我们每次枚举中间在哪个位置扔一次,答案就是碎和不碎两种情况的 \(max\) ,再将所有枚举的情况取 \(min\) 。
\[dp_{i,j} = \min \{dp_{i,j},\max\{dp_{k - 1,j - 1},dp_{i - k,j}\} + 1\} \]Code
#include<bits/stdc++.h>
using namespace std;
int n,m,dp[101][101];//dp[i][j]:硬度在[0,i]之间,还有J个鸡蛋
int main()
{
while(cin>>n>>m)
{
memset(dp,0x3f,sizeof(dp));
for(int i=1;i<=n;i++)
dp[i][1]=i;//只有一个鸡蛋,扔i次
for(int i=1;i<=m;i++)
dp[1][i]=dp[0][i]=1;//硬度只有2种情况,只扔1次
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
for(int k=1;k<=i;k++)
dp[i][j]=min(dp[i][j],max(dp[k-1][j-1],dp[i-k][j])+1);
cout<<dp[n][m]<<endl;
}
return 0;
}
如果这题数据加强到 \(10^{18}\) 我们怎么做呢?
考虑到 \(k \geq \lceil\log n\rceil\) 时,我们只需要二分来扔鸡蛋,就是最优选择。
鸡蛋数很少时,我们沿用第一种 \(dp\) 方程,发现 \(f_i(j)\) 是一个关于 \(j\) 的多项式,由于每次 \(f_i\) 近似于 \(f_{i - 1}\) 的前缀和,所以每次次数增加 \(1\) ,这是一个 \(i\) 次多项式,我们求的多项式次数很小,只有大约 \(64\) 次,但是自变量很大,可以达到 \(10^{18}\) 。提前计算次数 \(+ 1\) 个值后每次询问拉格朗日插值即可。
#include<bits/stdc++.h>
using namespace std;
const int N = 105;
typedef long long ll;
const ll inf = 1000000000000000005;
long long f[N][N];
inline void prework()
{
for(int i = 1;i <= 60;i++) f[1][i] = i;
for(int i = 2;i <= 58;i++)
{
f[i][1] = 1;
for(int j = 2;j <= 60;j++)
f[i][j] = f[i - 1][j - 1] + 1 + f[i][j - 1];
}
}
inline long double lag(ll p,ll X)
{
long double jdg = 0;
for(int i = 1;i <= p + 1;i++)
{
long double now = f[p][i];
for(int j = 1;j <= p + 1;j++)
{
if(j == i) continue;
now *= 1.00 * (X - j) / (i - j);
}
jdg += now;
if(jdg > inf) return inf;
}
return jdg;
}
inline ll lg(ll x)
{
ll ret = 1;
if((x & (-x)) == x) --ret;
for(;x;x /= 2) ++ret;
return ret;
}
int main()
{
long long n,k;
prework();
while(cin>>n>>k)
{
if(k == 1) {cout<<n<<endl; continue;}
if(k >= 59)
{
cout<<lg(n);
continue;
}
int l = 1,r = 4000005;
while(l < r)
{
int mid = (l + r) >> 1;
if(lag(k,mid) < n) l = mid + 1;
else r = mid;
}
cout<<l<<endl;
}
return 0;
}
之所以二分上界是 \(4 \times 10^6\) 是因为 \(k \geq 2\) 时答案不会超过这个数字,插值时如果当前答案大于 \(inf\) 就直接返回 \(inf\) 因为最终要做的只是比大小。
当然这题可以前缀和优化 \(dp\) 到 \(\Theta (n)\) ,然后直接 \(dp\) 到 \(4 \times 10^6\) ,只有 \(k = 2\) 时会接近这个数字,其他的都远小于,所以也可以做。
标签:游戏,int,鸡蛋,高度,long,换维,ll,dp From: https://www.cnblogs.com/TheLastCandy/p/17703673.html