写在代码之前
这三题是书中的最后三题,把它们写在一块是因为三个问题的解法中都有对动态规划解法和对动态规划的四边形不等式优化的解法。问题是在与看了一些动态规划的四边形不等式优化讲解之后还是没有特别理解,在此做个小记录,后面有时间再进一步分析。
丢棋子问题
题目
一座大楼有0~N层,地面算第0层,最高一层为第N层,已知棋子从第0层掉落肯定不会摔碎,从第i层掉落可能摔碎,也可能不会摔碎(1<=i<=N)。给定整数N作为楼层数,再给定整数K作为棋子数,返回如果想找到棋子不会摔碎的最高层数,即使在最差的情况下扔的最少次数,一次只能扔一个棋子。
详细分析参考书籍,根据书上JAVA代码写出C++代码
代码
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
/*暴力递归,时间复杂度会达到O(N!)*/
int process1(int nLevel, int k)
{
if (nLevel == 0)
return 0;
if (k == 1)
return nLevel;
int Min = INT_MAX;
for (int i = 1; i != nLevel + 1; i++)
{
Min = min(Min, max(process1(i - 1, k - 1), process1(nLevel - i, k)));
}
return Min + 1;
}
int solution1(int nLevel, int k)
{
if (nLevel < 1 || k < 1)
return 0;
return process1(nLevel, k);
}
/*动态规划,对于每个位置枚举时间复杂度为O(N),递归过程中,一共有N×K个位置要枚举,O(N*k)
所以总时间复杂度为O(N^2*K)*/
int solution2(int nLevel, int k)
{
if (nLevel < 1 || k < 1)
return 0;
if (k == 1)
return nLevel;
vector<vector<int>> dp(nLevel + 1, vector<int>(k + 1, 0));
for (int i = 1; i != nLevel + 1; i++)
dp[i][1] = i;
for (int i = 1; i != nLevel + 1; i++)
{
for (int j = 2; j != k + 1; j++)
{
int Min = INT_MAX;
for (int k = 1; k != i + 1; k++)
{
Min = min(Min, max(dp[k - 1][j - 1], dp[i - k][j]));
}
dp[i][j] = Min + 1;
}
}
return dp[nLevel][k];
}
/*动态规划,空间压缩*/
int solution3(int nLevel, int k)
{
if (nLevel < 1 || k < 1)
return 0;
if (k == 1)
return nLevel;
vector<int>preArr(nLevel + 1, 0);
vector<int>curArr(nLevel + 1, 0);
for (int i = 1; i != nLevel + 1; i++)
{
preArr[i] = 0;
curArr[i] = i;
}
for (int i = 1; i != k; i++)
{
vector<int> tmp = preArr;
preArr = curArr;
curArr = tmp;
for (int j = 1; j != nLevel + 1; j++)
{
int Min = INT_MAX;
for (int k = 1; k != j + 1; k++)
Min = min(Min, max(preArr[k - 1], curArr[j - k]));
curArr[j] = Min + 1;
}
}
return curArr[nLevel];
}
/*四边形不等式及相关猜想优化???*/
int solution4(int nLevel, int k)
{
if (nLevel < 1 || k < 1)
return 0;
if (k == 1)
return nLevel;
vector<vector<int>> dp(nLevel + 1, vector<int>(k + 1, 0));
for (int i = 1; i != nLevel + 1; i++)
dp[i][1] = i;
vector<int>cands(k + 1, 0);
for (int i = 1; i != k + 1; i++)
{
dp[1][i] = 1;
cands[i] = 1;
}
for (int i = 2; i < nLevel + 1; i++)
{
for (int j = k; j > 1; j--)
{
int Min = INT_MAX;
int minEnum = cands[j];
int maxEnum = j == k ? i / 2 + 1 : cands[j + 1];
for (int k = minEnum; k < maxEnum + 1; k++)
{
int cur = max(dp[k - 1][j - 1], dp[i - k][j]);
if (cur <= Min)
{
Min = cur;
cands[j] = k;
}
}
dp[i][j] = Min + 1;
}
}
return dp[nLevel][k];
}
/*反向看,k个棋子如果扔M次最多可以解决多少层楼的问题
map[i][j]意义是i个棋子仍j次最多搞定的楼层数*/
int log2N(int n)
{
int res = -1;
while (n != 0)
{
res++;
n >>= 1;
}
return res;
}
int solution5(int nLevel, int k)
{
if (nLevel < 1 || k < 1)
return 0;
int bTimes = log2N(nLevel) + 1;
if (k >= bTimes)
return bTimes;
vector<int> dp(k, 0);
int res = 0;
while (true)
{
res++;
int previous = 0;
for (int i = 0; i < k; i++)
{
int tmp = dp[i];
dp[i] = dp[i] + previous + 1;
previous = tmp;
if (dp[i] >= nLevel)
return res;
}
}
}
int main()
{
int level, k;
cin >> level >> k;
int res1 = solution1(level, k);
int res2 = solution2(level, k);
int res3 = solution3(level, k);
int res4 = solution4(level, k);
int res5 = solution5(level, k);
cout << res1 << " " << res2 << " ";
cout << res3 << " " << res4 << " " << res5 << endl;
getchar();
return 0;
}
画匠问题
题目
给定一个整型数组arr,数组中每个值都为正数,表示完成一幅画作需要的时间,再给定一个整数num表示画匠的数量,每个画匠只能画连在一起的画作。所有的画家并行工作,请返回完成所有的化作需要的最少时间。
举例:arr=[1, 1, 1,4,3],num=3.最好的分配方式为第一个画匠画前三个1,所需时间为3.第二个画匠画4,所需时间为4.第三个画匠画3,时间为3.因为并行工作,所以最少时间为4.
代码
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
/*动态规划,空间压缩
时间复杂度为O(N^2*num)*/
int solution1(vector<int>arr, int num)
{
if (arr.size() < 1 || num < 1)
return -1;
int len = arr.size();
vector<int>sumArr(len, 0);
vector<int>map(len, 0);
sumArr[0] = arr[0];
map[0] = arr[0];
for (int i = 1; i < len; i++)
{
sumArr[i] = sumArr[i - 1] + arr[i];
map[i] = sumArr[i];
}
for (int i = 1; i < num; i++)
{
for (int j = len - 1; j > i - 1; j--)
{
int Min = INT_MAX;
for (int k = i - 1; k < j; k++)
{
int cur = max(map[k], sumArr[j] - sumArr[k]);
Min = min(Min, cur);
}
map[j] = Min;
}
}
return map[len - 1];
}
/*四边形不等式的方法依旧模糊*/
int solution3(vector<int> arr, int num)
{
if (arr.size() < 1 || num < 1)
return -1;
int len = arr.size();
vector<int>sumArr(len, 0);
vector<int>map(len, 0);
sumArr[0] = arr[0];
map[0] = arr[0];
for (int i = 1; i < len; i++)
{
sumArr[i] = sumArr[i - 1] + arr[i];
map[i] = sumArr[i];
}
vector<int>cands(len, 0);
for (int i = 1; i < num; i++)
{
for (int j = len - 1; j > i - 1; j--)
{
int minPar = cands[j];
int maxPar = j == len - 1 ? j : cands[j + 1];
int Min = INT_MAX;
for (int k = minPar; k < maxPar + 1; k++)
{
int cur = max(map[k], sumArr[j] - sumArr[k]);
if (cur <= Min)
{
Min = cur;
cands[j] = k;
}
}
map[j] = Min;
}
}
return map[len - 1];
}
/*考虑每个画匠不能超过的时限limit*/
int getNeedNum(vector<int> arr, int lim)
{
int res = 1;
int stepSum = 0;
int len = arr.size();
for (int i = 0; i != len; i++)
{
if (arr[i] > lim)
return INT_MAX;
stepSum += arr[i];
if (stepSum > lim)
{
res++;
stepSum = arr[i];
}
}
return res;
}
int solution2(vector<int> arr, int num)
{
if (arr.size() < 1 || num < 1)
return -1;
int len = arr.size();
if (len < num)
{
int Max = INT_MIN;
for (int i = 0; i < len; i++)
Max = max(Max, arr[i]);
return Max;
}
else
{
int minSum = 0;
int maxSum = 0;
for (int i = 0; i < len; i++)
maxSum += arr[i];
while (minSum != maxSum - 1)
{
int mid = (minSum + maxSum) / 2;
if (getNeedNum(arr, mid) > num)
{
minSum = mid;
}
else
maxSum = mid;
}
return maxSum;
}
}
int main()
{
int len, num;
cin >> len >> num;
vector<int> input(len, 0);
for (int i = 0; i < len; i++)
cin >> input[i];
int res1 = solution1(input, num);
int res2 = solution2(input, num);
int res3 = solution3(input, num);
cout << res1 << " " << res2 << " " << res3 << endl;
getchar();
return 0;
}
邮局选址问题
题目
一条直线上有居民点,邮局只能建在居民点上。给定一个有序整型数组arr,每个值表示居民点的一维坐标,再给定一个正数num,表示邮局数量。选择num个居民点建立num个邮局,使所有的居民点到邮局的总距离最短,返回最短的总距离。
举例:arr=[1, 2, 3, 4, 5, 1000],num=2。第一个邮局建立子啊3位置,第二个邮局建立在1000位置,那么1位置到邮局距离为1,2位置到邮局距离为1,3位置到邮局距离为0,4位置到邮局距离为1,5位置到邮局距离为2,1000到邮局距离为0.所以以下方案总距离为6,返回6即可。
代码
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
int minDistance1(vector<int> arr, int num)
{
if (arr.size() < 1 || arr.size() < num)
return 0;
int len = arr.size();
vector<vector<int>> w(len + 1, vector<int>(len + 1, 0));
for (int i = 0; i < len; i++)
{
for (int j = i + 1; j < len; j++)
w[i][j] = w[i][j - 1] + arr[j] - arr[(i + j) / 2];
}
vector<vector<int>> dp(num, vector<int>(len, 0));
for (int j = 0; j < len; j++)
dp[0][j] = w[0][j];
for (int i = 1; i < num; i++)
{
for (int j = i + 1; j < len; j++)
{
dp[i][j] = INT_MAX;
for (int k = 0; k <= j; k++)
dp[i][j] = min(dp[i][j], dp[i - 1][k] + w[k + 1][j]);
}
}
return dp[num - 1][len - 1];
}
/*四边形不等式优化:*/
int minDistance2(vector<int> arr, int num)
{
if (arr.size() < 1 || arr.size() < num)
return 0;
int len = arr.size();
vector<vector<int>> w(len + 1, vector<int>(len + 1, 0));
for (int i = 0; i < len; i++)
{
for (int j = i + 1; j < len; j++)
w[i][j] = w[i][j - 1] + arr[j] - arr[(i + j) / 2];
}
vector<vector<int>> dp(num, vector<int>(len, 0));
vector<vector<int>> s(num, vector<int>(len, 0));
for (int j = 0; j != len; j++)
{
dp[0][j] = w[0][j];
s[0][j] = 0;
}
int minK = 0;
int maxK = 0;
int cur = 0;
for (int i = 1; i < num; i++)
{
for (int j = len - 1; j > i; j--)
{
minK = s[i - 1][j];
maxK = j == len - 1 ? len - 1 : s[i][j + 1];
dp[i][j] = INT_MAX;
for (int k = minK; k <= maxK; k++)
{
cur = dp[i - 1][k] + w[k + 1][j];
if (cur <= dp[i][j])
{
dp[i][j] = cur;
s[i][j] = k;
}
}
}
}
return dp[num - 1][len - 1];
}
int main()
{
int len, num;
cin >> len >> num;
vector<int>input(len, 0);
for (int i = 0; i < len; i++)
cin >> input[i];
int res1 = minDistance1(input, num);
int res2 = minDistance2(input, num);
cout << res1 << " " << res2 << endl;
getchar();
return 0;
}