题目链接在这里
A. Cheap Travel
题意: n站地铁,有两种买票的方式,a):一次一站a元,b):一次m站b元。问如何搭配可以最省钱。
分析: 有三种坐车的方式, a, b, a&b,找个最小值即可。
int main()
{
ios::sync_with_stdio(0);
int n, m, a, b;
cin >> n >> m >> a >> b;
int ans1 = n * a;
int c = n / m;
int r = n - m * c;
int ans2 = c * b + r * a;
int ans3 = (n % m) ? (c + 1) * b : c * b;
cout << min(ans1, min(ans2, ans3)) << endl;
return 0;
}
B. Wonder Room
题意: axb的宿舍,n个人住进去需要6xn的面积,如果不够可以增加宽高,问如何增加使得能住进去同时面积最小。
分析: 注意范围:1≥n, a, b≤109,因此理论上讲a的范围是[a, 6xn/b],b的范围是[b, 6n/a],在这个范围里搜索会超时。可以发现其中会有一些重复对。如果目标是构造6xn的面积,可以想到是短边≤sqrt(6xn),长边≥sqrt(6xn),刚好≥6xn也可以类似的思路。即短边的范围≤sqrt(6xn)。这样就可以避免重复对了。
int main()
{
ios::sync_with_stdio(0);
LL n, a, b;
cin >> n >> a >> b;
LL square = 6 * n;
LL s = 0, a1 = a, b1 = b;
bool swaped = false;
if (a > b) {
swaped = true;
swap(a, b);
}
for (LL i = a; i * i <= square; ++i) {
LL t = square / i;
if (square % i != 0)
++t;
if (t <= b)
break;
LL new_s = t * i;
if (s == 0 || new_s < s) {
s = new_s;
a1 = i;
b1 = t;
}
}
if (swaped) {
swap(a1, b1);
}
if (s == 0) {
s = a * b;
}
cout << s << endl;
cout << a1 << " " << b1 << endl;
return 0;
}
C. Number of Ways
题意: n长度的数组,要求分成三份,每份的元素和都是整个数组元素和的1/3, 问所有可能的组合数目。
分析: O(n)可以求出整个数组的元素和sum,因此题目变成求分割的办法使得每份都是sum/3。只要满足下列条件就可以分成三份:
假设从左往右求和,加到某个数和为 sum / 3成为2分点,和为sum * 2 / 3称为2分点。
- sum可以整除3.
- 2分点可以与前面任意一个1分点组合成一种办法。
- 1分点后面至少要有两个元素,2分点前面至少要有1个元素,后面至少要有一个元素。
否则就没法分成3份了。
int main()
{
ios::sync_with_stdio(0);
LL n, a[500005] = {0}, sum = 0, ans = 0;
cin >> n;
for (int i = 0; i < n; ++i) {
cin >> a[i];
sum += a[i];
}
LL k1 = 0;
if (sum % 3 == 0) {
LL s = 0, aver = sum / 3;
for (int i = 0; i < n; ++i) {
s += a[i];
if (2 * aver == s && i > 0 && i < n - 1) {
ans += k1;
}
if (aver == s && i < n - 2) {
k1++;
}
}
}
cout << ans << endl;
return 0;
}
D. Increase Sequence
题意: n长度的数组,a1, a2, …, an初始高度为1目标需要每个元素增加到高度h,增加的方式是选择一个闭区间[l,r],闭区间内所有元素高度+1,同时任意一个元素不能被选作左端点两次,也不能被选作右端点两次。
分析: 非常奇妙的一个DP的题目。解法里有一维和二维,从二维的角度比较容易理解。
首先dp[i][j]表示第i个元素处理完成同时还有j个左端点没有闭合的情况数。处理完成是指i元素增加到了h高度。
对i元素要满足有j个左端点处理的方式有5种:
- ”]” 即添加一个右端点 要满足有j个左端点,需要从[i-1][j+1]转化而来,j+1任意选择一个添加上右端点闭合即可。因此方法数j+1,处理后i元素增加的高度增加j+1。
- ”][” 即添加一个右端点,并且开启一个区间(添加一个左端点),由[i-1][j]转化而来,方法数j,处理后增加高度j+1。
- ”[” 即添加一个左端点,由[i-1][j-1]转化而来,方法数1,处理后高度增加j。
- ”[]” 即添加一个区间[i, i],由[i-1][j]转化而来,方法数1,处理后高度增加j+1。
- “N” 即什么都不做,由[i-1][j]转化而来,方法数1,处理后高度增加j。
同时需要满足的条件是增加的高度需要为h - a[i],如果不满足则可以认为方法数为0.
即对于方法1,2,4,增加的高度是j+1,我们需要这个值等于h - a[i],即j = h - a[i] - 1,因此关心的i-1的j’也能推算出来。
因此虽然我们开辟了二维的数组dp,但是O(n)就可以完成了。
看到有开辟一维数组的办法,没看懂,有路过的高手欢迎讲解下。
初始条件dp[0][0] = 1;即0个元素0个左端点的方法数为1。
最后的结果数放在dp[n][0],即所有元素迭代完,所有区间关闭。
总结一下的话就是下面几行
//操作 : 转化方法 : 转化后增加的高度
// ] : i,j = i-1,j+1 * j+1 : j+1
// ][ : i,j = i-1,j * j : j+1
// [ : i,j = i-1,j-1 : j
// [] : i,j = i-1,j : j+1
// N : i,j = i-1,j : j
int main()
{
ios::sync_with_stdio(0);
cin >> n >> h;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
a[i] = h - a[i];
}
dp[0][0] = 1;
for (int i = 1; i <= n; ++i) {
//a[i] height needed.
if (a[i] != 0) {
add_equal(dp[i][a[i] - 1], dp[i - 1][a[i]] * a[i]);
add_equal(dp[i][a[i] - 1], dp[i - 1][a[i] - 1] * (a[i] - 1));
add_equal(dp[i][a[i] - 1], dp[i - 1][a[i] - 1]);
add_equal(dp[i][a[i]], dp[i - 1][a[i] - 1]);
}
add_equal(dp[i][a[i]], dp[i - 1][a[i]]);
}
cout << dp[n][0] << endl;
return 0;
}