第一次写题解,然后今天怀着玄学的心去洛谷签了个到,宜:打模拟赛,AK碾压全场,虽然水平有限,但是今天做题居然真的出人意料的顺,拿了好几个深绿,来看详细内容:
B:
你有55种不同面值的硬币,每种硬币的价值等于前55个三角数中的一个: 1, 3, 6, 10, 和 15。这些硬币种类数量充足。你的目标是找到需要的最少硬币数量,使它们的总价值恰好为𝑛。
我们可以证明答案总是存在。
输入
第一行包含一个整数𝑡(1≤t≤)— 测试用例的数量。接下来是每个测试用例的描述。
每个测试用例的第一行包含一个整数n𝑛(1≤n≤)— 目标价值。
输出
对于每个测试用例,输出一个数字 — 需要的最少硬币数量。
示例
Inputcopy | Outputcopy |
---|---|
14 1 2 3 5 7 11 12 14 16 17 18 20 98 402931328 | 1 2 1 3 2 2 2 3 2 3 2 2 8 26862090 |
初一看,是十分简单的DP题,我也是很快码出了代码,如下:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e8 + 10;
const int inf = 0x3f3f3f3f;
int type[5] = {1,3,6,10,15};
int dp[N];
void solve()
{
memset(dp, inf, sizeof(dp));
dp[0] = 0;
for (int i = 0; i < 5; i++)
for (int j = type[i]; j <= N; j++)
dp[j] = min(dp[j], dp[j - type[i]] + 1);
}
int main()
{
solve();
int T;
cin >> T;
while (T --)
{
int n;
cin >> n;
cout << dp[n] << endl;
}
}
然后就MLE了,一看这数据确实有点大,然后我开始脑子一抽,一片空白,寄了。
后来写了5题后突然想到我为什么要一点一点算,数据不都有现成的吗,于是醍醐灌顶,大改了一番:
//经过实践得知先提前预处理一遍是不行的,因此我采用了舍弃大部头已知部分的只需要求取余量就行了,总的来说还是脑子不好使
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6+10;
int T, n, a[100];
void solve()
{
cin >> n;
int ans = n;
ans = min(ans, n / 15 + a[n % 15]);
ans = min(ans, n / 10 + a[n % 10]);
ans = min(ans, n / 6 + a[n % 6]);
ans = min(ans, n / 3 + a[n % 3]);
int k = (n - 15) / 15 * 15, m = n - k;
ans = min(ans, k / 15 + a[m]);
cout << ans << endl;
}
int main()
{
for(int i = 1; i <= 30; i ++)
a[i] = 100;
a[1] = 1, a[3] = 1, a[6] = 1, a[10] = 1, a[15] = 1;
for(int i = 1; i <= 30; i ++)
{
if(i > 1)
a[i] = min(a[i], a[i - 1] + 1);
if(i > 3)
a[i] = min(a[i], a[i - 3] + 1);
if(i > 6)
a[i] = min(a[i], a[i - 6] + 1);
if(i > 10)
a[i] = min(a[i], a[i - 10] + 1);
}
cin >> T;
while(T --)
solve();
}
这样一改,数据处理量大幅下降,因为如果一个数是对应某一个三角数的倍数,那么只需要在原来的基础上乘上倍数就可以了,DP还是要用的。
C:
给定一个包含 n𝑛 个元素的数组 a𝑎,找出表达式的最大值:
|𝑎𝑖−𝑎𝑗|+|𝑎𝑗−𝑎𝑘|+|𝑎𝑘−𝑎𝑙|+|𝑎𝑙−𝑎𝑖|
其中 𝑖、𝑗、𝑘 和 𝑙 是数组 𝑎 的四个 不同 下标,满足 1≤𝑖,𝑗,𝑘,𝑙≤𝑛。
这里 |𝑥| 表示 𝑥 的绝对值。
输入
第一行包含一个整数 t𝑡 (1≤𝑡≤500) — 表示测试用例的数量。接下来是每个测试用例的描述。
每个测试用例的第一行包含一个整数 𝑛 (4≤𝑛≤100) — 给定数组的长度。
每个测试用例的第二行包含 𝑛 个数 𝑎1,𝑎2,…,𝑎𝑛 (≤𝑎𝑖≤)。
输出
对于每个测试用例,输出一个整数 — 最大值。
示例
Inputcopy | Outputcopy |
---|---|
5 4 1 1 1 1 5 1 1 2 2 3 8 5 1 3 2 -3 -1 10 3 4 3 3 1 1 4 1 2 2 -1 | 0 6 38 8 8 |
这题确实是一题水题,只需要简单排序一下然后取队首,队尾的值对应就行了:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int T, n, a[N];
int main()
{
cin >> T;
while(T --)
{
cin >> n;
for(int i = 1; i <= n; i ++)
cin >> a[i];
sort(a + 1, a + n + 1);
cout << abs(a[n]-a[1])+abs(a[1]-a[n-1])+abs(a[n-1]-a[2])+abs(a[2]-a[n]) << endl;
}
}
E:
有一个长度为𝑛的一维网格。网格的第i𝑖个单元格包含一个字符𝑠𝑖,它要么是'<',要么是'>'。
当一个弹球被放置在其中一个单元格上时,它按照以下规则移动:
- 如果弹球在第𝑖个单元格上且𝑠𝑖是'<',则下一秒弹球向左移动一个单元格。如果𝑠𝑖是'>',则向右移动一个单元格。
- 弹球移动后,字符𝑠𝑖被反转(即如果𝑠𝑖原来是'<',则变为'>',反之亦然)。
- 当弹球离开网格时停止移动:要么从左边界离开,要么从右边界离开。
你需要回答𝑛个独立的查询。在第i𝑖个查询中,一个弹球将被放置在第𝑖个单元格上。请注意,我们总是将弹球放在初始网格上。
对于每个查询,计算弹球离开网格需要多少秒。可以证明弹球总会在有限步内离开网格。
输入
每个测试包含多个测试用例。第一行包含测试用例的数量t𝑡(1≤𝑡≤)。接下来是测试用例的描述。
每个测试用例的第一行包含一个整数n𝑛(1≤𝑛≤5⋅)。
每个测试用例的第二行包含一个长度为n𝑛的字符串𝑠1𝑠2…𝑠𝑛,由字符'<'和'>'组成。
保证所有测试用例中𝑛的总和不超过5⋅。
输出
对于每个测试用例,对于每个𝑖(1≤𝑖≤𝑛),输出当一个弹球最初放置在第i𝑖个单元格时的答案。
示例1
Inputcopy | Outputcopy |
---|---|
3 3 ><< 4 <<<< 6 <><<<> | 3 6 5 1 2 3 4 1 4 7 10 8 1 |
注意
在第一个测试用例中,弹球的移动情况如下图所示。弹球离开网格需要3秒。
弹球的移动情况如下图所示。弹球离开网格需要6秒。
说来也巧,这道题我之前看过题解,是CF上有一次的题,我之前闲着看的时候无意间看到过,这题其实是挺考验思维的,而我就挺会刷小聪明的,如果不是之前看过大佬的题解,可能这题也开不出来。
先附上代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1000010;
int T, n;
ll a[N], b[N], aa[N], bb[N];
char s[N];
int f(int x) {
int L = 0, R = n + 1, M;
while (L + 1 != R) {
M = (L + R) >> 1;
if (b[M] < x)
L = M;
else
R = M;
}
return R;
}
int ff(int x) {
int L = 0, R = n + 1, M;
while (L + 1 != R) {
M = (L + R) >> 1;
if (a[n] - a[M - 1] < x) R = M;
else L = M;
}
return L;
}
int main() {
scanf("%d", &T);
while (T--) {
scanf("%d %s", &n, s);
for (int i = 1; i <= n; i++)
{
b[i] = b[i - 1] + (s[i - 1] == '>');
a[i] = a[i - 1] + (s[i - 1] == '<');
bb[i] = bb[i - 1] + i * (s[i - 1] == '>');
aa[i] = aa[i - 1] + i * (s[i - 1] == '<');
}
for (int i = 1; i <= n; i++)
{
if (s[i - 1] == '>')
{
if (b[i] > a[n] - a[i])
{
int p = f(b[i] - (a[n] - a[i]));
printf("%lld ", 2 * ((aa[n] - aa[i]) - (bb[i] - bb[p - 1])) + i + (n + 1));
}
else
{
int p = ff((a[n] - a[i]) - b[i] + 1);
printf("%lld ", 2 * ((aa[p] - aa[i]) - (bb[i] - bb[0])) + i);
}
}
else
{
if (b[i] >= a[n] - a[i - 1])
{
int p = f(b[i] - (a[n] - a[i - 1]) + 1);
printf("%lld ", 2 * ((aa[n] - aa[i - 1]) - (bb[i] - bb[p - 1])) - i + (n + 1));
} else
{
int p = ff((a[n] - a[i - 1]) - b[i]);
printf("%lld ", 2 * ((aa[p] - aa[i - 1]) - (bb[i] - bb[0])) - i);
}
}
}
printf("\n");
}
}
之前我特别研究过这道题,因此看到的时候也是得心应手,这题采用的是前缀和加二分搜索进行实现的,通过前缀和进行快速的数据处理,将一致的命令信号归到一起,然后通过二分搜索快速处理,维护。因为这涉及到两个方面的知识,所以之后我会优先更新这两方面的内容。这里不做过多讲解。
F:
给定一个由零和一组成的2×𝑛 网格。设第 𝑖 行和第 𝑗 列的交点处的数字为 𝑎𝑖𝑗。
有一只蚱蜢位于左上角的单元格 (1,1),只能向右或向下跳一格。它想要到达右下角的单元格 (2,𝑛)。考虑由路径上的单元格中的数字构成的长度为 𝑛+1 的二进制字符串,保持其顺序不变。
你的目标是:
- 找到通过选择任意可用路径获得的字典序最小的†字符串;
- 找到产生这个字典序最小字符串的路径数量。
† 如果两个字符串 𝑠 和 𝑡 长度相同,则 𝑠 在第一个位置上与 𝑡 不同时,如果 𝑠 的元素比 𝑡 中对应的元素小,则 𝑠 字典序小于 𝑡。
输入
每个测试包含多个测试用例。第一行包含测试用例的数量 t𝑡 (1≤𝑡≤)。接下来是测试用例的描述。
每个测试用例的第一行包含一个整数 𝑛 (2≤𝑛≤2⋅)。
每个测试用例的第二行包含一个二进制字符串 𝑎11𝑎12…𝑎1𝑛 (a1i𝑎1𝑖 要么是 00,要么是 11)。
每个测试用例的第三行包含一个二进制字符串 𝑎21𝑎22…𝑎2𝑛 (a2i𝑎2𝑖 要么是 00,要么是 1)。
保证所有测试用例中的 𝑛 总和不超过 2⋅。
输出
对于每个测试用例,输出两行:
- 通过选择任意可用路径获得的字典序最小的字符串;
- 产生这个字符串的路径数量。
示例 1
Inputcopy | Outputcopy |
---|---|
3 2 00 00 4 1101 1100 8 00100111 11101101 | 000 2 11000 1 001001101 4 |
注意
在第一个测试用例中,字典序最小的字符串是 000000。有两条路径可以产生这个字符串:
在第二个测试用例中,字典序最小的字符串是 1100011000。只有一条路径可以产生这个字符串:
初看这题,仿佛需要用到搜索方面的知识,但是通过简单的观察和思考,其实也挺水的。由于每一次只能往下和往右,所以其实我们只需要找到一个特殊点,从这个点往下走即可。如果右侧字符小于下面字符,一定向右走,这个位置记为𝑥。如果右侧字符大于下面字符,此时一定得向下走,这个位置记为𝑦。
我们在[𝑥+1,𝑦]之间随时都可以向下走,这样得到的都是最小字典序字符串。
因此:
#include<bits/stdc++.h>
using namespace std;
int T, n, x, y;
int main()
{
cin >> T;
while (T --)
{
string a, b, ans;
cin >> n;
cin >> a >> b;
a = ' ' + a;
b = ' ' + b;
y = n, x = 0;
for (int i = 1; i < n; i++)
{
if (b[i] < a[i + 1])
{
y = i;
break;
}
if (b[i] > a[i + 1])
x = i;
}
if (y == n && x == 0)
{
ans = a + b.back();
cout << ans << endl;
cout << n << endl;
}
else
{
ans = a.substr(1, y) + b.substr(y);
cout << ans << endl;
if (!x)
cout << y << endl;
else
cout << y - x << endl;
}
}
}
G:
给定一个数组 𝑎1,𝑎2,…,𝑎𝑛。初始时,𝑎𝑖=𝑖 每个 1≤𝑖≤𝑛。
对于整数 𝑘≥2,操作 swap(𝑘) 定义如下:
- 设 𝑑 是 𝑘 的最大非本身约数。然后交换元素 𝑎𝑑 和 𝑎𝑘。
假设按照给定顺序对每个𝑖=2,3,…,𝑛 执行 swap(𝑖)。找到 1 在结果数组中的位置。换句话说,找到这样的 j𝑗,使得执行这些操作后 𝑎𝑗=1。
† 整数 𝑥 是 𝑦 的约数,如果存在整数 𝑧 满足 𝑦=𝑥⋅𝑧。
输入
每个测试包含多个测试用例。第一行包含测试用例的数量 𝑡 (1≤𝑡≤)。接下来是测试用例的描述。
每个测试用例的唯一行包含一个整数 𝑛 (1≤𝑛≤) — 数组 𝑎 的长度。
输出
对于每个测试用例,输出 11 在结果数组中的位置。
示例 1
Inputcopy | Outputcopy |
---|---|
4 1 4 5 120240229 | 1 4 4 67108864 |
注意
在第一个测试用例中,数组是 [1],没有执行任何操作。
在第二个测试用例中,𝑎 的变化如下:
- 初始时,𝑎 是[1,2,3,4]。
- 执行 swap(2) 后,𝑎 变为 [2_,1_,3,4] (被划线的元素被交换)。
- 执行 swap(3) 后,𝑎 变为 [3_,1,2_,4]。
- 执行 swap(4) 后,𝑎 变为 [3,4_,2,1_]。
最终,元素 1 位于索引 4 (即,𝑎4=1)。因此,答案是 4。
应该是最水的一题,我们不难发现 1 每次只会移动到2的n次幂位置上,我们可以用位运算,十分简洁:
#include<bits/stdc++.h>
using namespace std;
int T, n;
int main()
{
cin >> T;
while (T --)
{
cin >> n;
for (int i = 30; i >= 0; i--)
{
if (n >= (1 << i))
{
cout << (1 << i) << endl;
break;
}
}
}
}
本来这场是8开6的,但是有一个交互题是我去网上现场学习的,凭借自己浅薄的认识居然闯过去了,从本质上我并没有将其深刻铭记,所以不予评价,到有一天我真正搞明白后我在来讲这交互题。