【动态规划】【换维】扔鸡蛋游戏

【动态规划】【换维】扔鸡蛋游戏

这是一道在《信息学奥赛一本通》上的经典题目。题目描述如下:

k 个一模一样的鸡蛋,楼的高度为 n ,定义鸡蛋的硬度为 x ,当且仅当将鸡蛋从 x 楼扔下不会碎,从 x+1 楼扔下会碎,求最坏情况下求出鸡蛋硬度的最小步数。

k10,n100 。保证硬度在 [0,n] 之间。

考虑假设只有两个蛋,最小步数是 t ,我们的策略是怎样的:

image

我们一定是分块来扔,考虑如果第一次碎了,那么硬度一定小于第一次扔的高度,此时我们还有 t1 次,可以确定 t1 个位置(扔 1t1 ,如果 1 碎了就是 0),所以第一次的高度选为 t 。后面同理,只不过每次都比上一次少了 1 。最后能确定的高度上限是 t(t+1)2

接下来假设我们有 3 个蛋,假设第一次高度为 x 。如果碎了,我们就要用 2 个蛋和 t1 次来确定剩下的高度,假设 fi,ji 个蛋 j 次能达到的高度。这个高度 x 就等于 f2,t1+1 。如果没碎,我们就要用 3 个蛋和 t1 次确定上面的高度,所以是 f3,t1

综上所述, dp 式子已经很完全了,就是:

fi,j=fi1,j1+1+fi,j1

直接做即可,这道题目难以通过枚举高度和鸡蛋个数来算出次数,所以我们考虑换一维,用个数和次数推出最高高度,再通过二分回答询问,是一种很巧妙的思路,时间复杂度 Θ(kn)

当然还有基于枚举高度的 Θ(n2k) 的做法,设 fi,j 为硬度在 [0,i] 之间,还有 j 个鸡蛋时的最小次数,我们每次枚举中间在哪个位置扔一次,答案就是碎和不碎两种情况的 max ,再将所有枚举的情况取 min

dpi,j=min{dpi,j,max{dpk1,j1,dpik,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;
 }

如果这题数据加强到 1018 我们怎么做呢?

考虑到 klogn 时,我们只需要二分来扔鸡蛋,就是最优选择。

鸡蛋数很少时,我们沿用第一种 dp 方程,发现 fi(j) 是一个关于 j 的多项式,由于每次 fi 近似于 fi1 的前缀和,所以每次次数增加 1 ,这是一个 i 次多项式,我们求的多项式次数很小,只有大约 64 次,但是自变量很大,可以达到 1018 。提前计算次数 +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×106 是因为 k2 时答案不会超过这个数字,插值时如果当前答案大于 inf 就直接返回 inf 因为最终要做的只是比大小。

当然这题可以前缀和优化 dpΘ(n) ,然后直接 dp4×106 ,只有 k=2 时会接近这个数字,其他的都远小于,所以也可以做。

posted @   The_Last_Candy  阅读(60)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示