题目链接:poj 3783
题意分析:
经典题,紫书上的一道例题,4+2出了这道原题,我愣是以为是数学题,最后也没做出来。题意是这样的,给你N个鸡蛋(硬度一样),让你测鸡蛋的硬度,测量的方法就是从某栋M层的楼的某一层X上把鸡蛋扔下来,如果鸡蛋碎了,代表他的强度小于X;如果没碎,则强度大于等于X。我们要做的就是不断的从楼上把鸡蛋扔下来,直到找到某一层楼X,从这一层楼扔下来鸡蛋不碎掉,从X+1层扔下来鸡蛋碎掉,那么鸡蛋的强度就是X。如果在M层扔下来鸡蛋也不碎掉,那么鸡蛋的强度为M。问题是,用N个鸡蛋最多需要几步可以把硬度测出来(鸡蛋碎掉就不能用了,而且必须测出来!)。
其实真正的问题是,对于硬度取值范围为[0, M]的鸡蛋,N次蛋碎的机会,最多需要多少步一定可以测出鸡蛋的硬度。
解题思路:
一开始看懂题意之后,我马上就想到了二分法,但是仔细一想并不对,如果鸡蛋的数量足够的话,二分法绝对是最快的,但是如果鸡蛋不够,就不太一样了。如果第一次扔就碎了一个鸡蛋,那么剩下的鸡蛋就只能从下往上测试,这样2个鸡蛋、100层楼的情况最多可能需要51步,然而测试样例告诉我们,只要14步,当时想了足足有半个钟才想到他可以用{ 14,13,12 …… }这样的步长进行测试,这样最多的测试数都是14次,这样的原则确确实实可以解决2个鸡蛋的问题,但是3个鸡蛋就跪了,于是有了动态规划算法。
动态规划算法:
状态:
dp[i][j]表示N=i,M=j时,最多需要多少次测试一定可以测出鸡蛋的硬度。
状态转移方程:
如果我们一开始是在k层进行测试的,那么如果鸡蛋破碎了,我们的查找范围就变成k层以下的k-1层,当然此时鸡蛋数减少了,所以最终的步数应该为dp[i-1][k-1]+1;另外一种情况是鸡蛋没有碎的情况,我们要找的范围变成了k层以上的,所以最终需要dp[i][j-k]+1步。我们的目标就是要找到一个k,使得最坏情况下测试数最少,所以我们需要枚举k。代码如下:
for(int i=2;i<=MAXN;i++)
{
for(int j=2;j<=MAXM;j++)
{
for(int k=1;k<j;k++)
{
dp[i][j]=min(dp[i][j],max(dp[i-1][k-1]+1,dp[i][j-k]+1));
}
}
}
初始状态:
N=1时,测试数为M;M=1,测试数为1;M=0,测试数为0;
AC代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define INF 0x3f3f3f3f
#define MAXN 55
#define MAXM 1005
using namespace std;
int n,m,dp[55][1005];
int main()
{
int T,t;
scanf("%d",&T);
memset(dp,INF,sizeof(dp));
for(int i=1;i<=MAXM;i++)
dp[1][i]=i;
for(int i=1;i<=MAXN;i++)
dp[i][1]=1,dp[i][0]=0;
for(int i=2;i<=MAXN;i++)
{
for(int j=2;j<=MAXM;j++)
{
for(int k=1;k<j;k++)
{
dp[i][j]=min(dp[i][j],max(dp[i-1][k-1]+1,dp[i][j-k]+1));
}
}
}
while(T--)
{
scanf("%d %d %d",&t,&n,&m);
printf("%d %d\n",t,dp[n][m]);
}
return 0;
}
总结:
其实如果知道是用动态规划做的话,状态什么的还是挺好想的,但是关键是想不到啊~~经验不足真可怕,不过被时间复杂度吓到也是一部分原因,居然没想到离线。
1、多坐点题积累经验吧;
2、不要被复杂度吓到;
3、事实证明,智商很重要TAT