AtCoder Beginner Contest 364 Review
-1. 前言
我!进!前!250!了!!!
0. 沉浸体验
20:00 噫!好!我网没卡!(被 \(7\) 月 \(23\) 日的 CF 整到 PTSD 的我的反应)
A. Glutton Takahashi 这啥玩意
哦!直接上 map 判断 sweet 的数量是不是大于 \(\lceil\frac{n}{2}\rceil\) 就行了!
打完 Winter Camp! 没过样例!
再看一眼题目 Winter Camp! 不能改变顺序!
删掉代码重新打完 好!样例过了!交!
20:02:56 过。
B. Grid Walk 布什戈门,第二题就让我打模拟是吧
好!打!
打完 好!样例过了!交!
20:06:31 过。
C. Minimum Glutton 怎么 Glutton 又出现了
诈骗是吧,故意让我们把 \(A_i\) 和 \(B_i\) 绑在一起然后不说这两东西可以分离
疑?悟!
打完 啊?没过样例?
乱改参数 啊?还是没过?
静态查错 Winter Camp! \(n-i+1\) 打成 \(i\) 了!
改过来 好!过样例了!交!
21:12:25 过。
D. K-th Nearest 啊?
布什戈门,怎么 D 就上序列维护问题啊
仔细再看一次题目 Winter Camp! 理解错题目了!直接将 \(a\) 排序然后二分答案用 STL 辅助 check 就可以了!我是 Subtask!
打完 啊?怎么没过样例?
仔细看一眼样例 我代码也没问题啊?
再仔细看一眼样例 Winter Camp! 二分区间右端点设小了!每次都要设成 \(2\times 10^8\)!我是 Subtask!
改完代码 好!过样例了!交!
20:19:08 过。
E. Maximum Glutton 你 Glutton 都出现三次了是不是图谋不轨啊
啊?二重 Meet-in-Middle?啊?(此时没想到“那个”解法)
不会,换一题。
F. Range Connect MST MST,最小生成树,想起了一些不好的过往
啊?线段树辅助建图上最小生成树?啊?
不会,换一题。
G. Last Major City 这……莫名有种宿命感
啊?我记得我好像做过类似的题?啊?
忘了怎么写了啊啊啊啊啊啊啊啊啊
回去想 F 吧
冷静,这可是 AtCoder,不会有大码量毒瘤题的
嗯?没有大码量?也就是说没有线段树?
想起了之前自己出的差分题 啊啊啊啊,这题果然不用线段树!
直接标记每条边所属的节点编号,边权,以及其出现和消失的位置,然后加一个并查集维护信息就可以了!我是 Subtask!
打完 啊?运行错误?
输出变量 啊?问题出在启发式合并环节?
静态查错 Winter Camp! 我少打了一个字母!
改好 好!过样例了!交!
20:55:02 过!没想到跑得这么快!
回去看 E 还是不行啊,\(nxy\) 铁定会超的
突然回想起之前学弟出的一道题 尤里卡!倒反天罡!
其实我们不用拘泥于求出每个总甜/咸度值的可行性,而是求出在吃掉 \(i\) 个菜品且甜度为 \(j\) 时最小的咸度值 \(d_{i,j}\)!
然后,时间复杂度就成功地降至了 \(O(n^2x)\)!这下肯定能过!
打完 啊?样例错了?
突然想到输出的答案要加一 然后过了两个样例。
那还是有问题啊。
静态查错 Winter Camp! 打错了两个字符!
改完 好!过样例了!交!
21:12:01 过!用时竟然这么少!
然后后面就是摆烂了。G 不会一点。
下面进入正题。
1. Glutton Takahashi
模拟即可。
显然,当两个 sweet 同时出现在末尾时,答案为 Yes,否则是 No。
#include <iostream>
#include <string>
#include <map>
using namespace std;
const int N = 1e5 + 10;
int n;
string s, ps;
map<string, int> mp;
bool fl;
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> s;
if (fl)
return puts("No"), 0;
if (s == "sweet" and ps == "sweet")
fl = true;
ps = s;
}
puts("Yes");
}
2. Grid Walk
模拟。
题面过于详细,直接按照题目实现即可。
#include <iostream>
#include <string>
#include <map>
using namespace std;
const int N = 5e2 + 10;
int n, m, x, y, nx, ny;
char mp[N][N];
map<char, pair<int, int>> tmp;
string s;
int main()
{
cin >> n >> m >> x >> y;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
cin >> mp[i][j];
}
}
cin >> s;
tmp['L'] = {0, -1};
tmp['R'] = {0, 1};
tmp['U'] = {-1, 0};
tmp['D'] = {1, 0};
for (auto &i : s)
{
auto &[fx, fy] = tmp[i];
nx = x + fx, ny = y + fy;
if (nx < 1 or nx > n or ny < 1 or ny > m or mp[nx][ny] == '#')
continue;
x = nx, y = ny;
}
printf("%d %d\n", x, y);
}
3. Minimum Glutton
为了让 Takahashi 更早结束,我们显然只需要考虑其中一种属性。
将甜度与咸度分离,分别排序,取符合条件的最小值即可。
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 2e5 + 10;
int n;
using ll = long long;
ll x, y;
int a[N], b[N], res;
int main()
{
scanf("%d%lld%lld", &n, &x, &y);
res = n;
for (int i = 1; i <= n; i++)
{
scanf("%d", a + i);
}
for (int i = 1; i <= n; i++)
{
scanf("%d", b + i);
}
sort(a + 1, a + n + 1);
sort(b + 1, b + n + 1);
for (int i = n; i; i--)
{
x -= a[i];
if (x < 0)
{
res = min(res, n - i + 1);
break;
}
}
for (int i = n; i; i--)
{
y -= b[i];
if (y < 0)
{
res = min(res, n - i + 1);
break;
}
}
printf("%d\n", res);
}
4. K-th Nearest
一维数轴上 \(k\) 小距离问题。
将 \(a\) 排序后借助 STL 进行二分,求得符合各个距离限度的点数量,进行二分逼近即可。
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10;
int n, q, a[N], b, k, l, r, mid, tr = 2e8;
int main()
{
scanf("%d%d", &n, &q);
for (int i = 1; i <= n; i++)
{
scanf("%d", a + i);
}
sort(a + 1, a + n + 1);
// tr = a[n] - a[1];
for (int i = 1; i <= q; i++)
{
scanf("%d%d", &b, &k);
l = 0, r = tr;
while (l < r)
{
mid = (l + r) >> 1;
if (upper_bound(a + 1, a + n + 1, b + mid) - lower_bound(a + 1, a + n + 1, b - mid) < k)
l = mid + 1;
else
r = mid;
}
printf("%d\n", l);
}
}
5. Maximum Glutton
最直观的 DP 方法是设 \(d_{i,j}\) 表示甜度为 \(i\) 且咸度为 \(j\) 的可行性。
但是,这样的时间复杂度是 \(O(nxy)\),显然是不可接受的。
为了解决问题,我们需要进行一个倒反天罡。
设 \(d_{i,j}\) 表示 \(i\) 个菜品甜度为 \(j\) 时的最小咸度。这样设计的好处是,将单次转移所用的时间复杂度从 \(O(xy)\) 降至 \(O(nx)\)。最终求可行性时,只需要满足 \(d_{i,j}\le y\) 就可以了。
时间复杂度 \(O(n^2x)\)。
#include <iostream>
#include <cstring>
using namespace std;
const int N = 1e2 + 10, M = 1e4 + 10;
int dp[N][M], n, a, b, x, y;
int main()
{
scanf("%d%d%d", &n, &x, &y);
memset(dp, 0x3f, sizeof dp);
for (int i = 0; i <= x; i++)
dp[0][i] = 0;
for (int i = 1; i <= n; i++)
{
scanf("%d%d", &a, &b);
for (int j = i; j; j--)
{
for (int k = x; k >= a; k--)
{
dp[j][k] = min(dp[j][k], dp[j - 1][k - a] + b);
}
}
}
for (int i = n - 1; i; i--)
{
for (int j = 0; j <= x; j++)
{
// printf("%d %d %d\n", i, j, dp[i][j]);
if (dp[i][j] <= y)
return printf("%d\n", i + 1), 0;
}
}
puts("1");
}
6. Range Connect MST
根据题目可以得出,从每个点连出的边的边权是完全一致的。
换句话说,我们可以对在同一连通块内的所有节点只选一条最短的边连接。信息可以借助并查集、map 以及 multiset 进行合并。
可以借助差分的思想,在区间左端点插入,在区间右端点删除。这里的区间是左闭右开的。
时间复杂度 \(O((n+q)\log n)\)。
#include <iostream>
#include <map>
#include <set>
#include <vector>
using namespace std;
const int N = 4e5 + 10;
int n, f[N], siz[N], q, l, r, c;
inline int find(int x)
{
return x == f[x] ? x : f[x] = find(f[x]);
}
inline void merge(int x, int y)
{
x = find(x), y = find(y);
if (x == y)
return;
f[y] = x;
siz[x] += siz[y];
siz[y] = 0;
}
using pii = pair<int, int>;
vector<pii> vs[N], er[N];
map<int, multiset<int>> mp, tmp;
using ll = long long;
ll res;
int main()
{
scanf("%d%d", &n, &q);
for (int i = 1; i <= q; i++)
{
scanf("%d%d%d", &l, &r, &c);
vs[l].push_back({n + i, c});
er[r + 1].push_back({n + i, c});
}
for (int i = 1; i <= n + q; i++)
f[i] = i, siz[i] = 1;
for (int i = 1; i <= n; i++)
{
for (auto [j, tv] : er[i])
{
mp[find(j)].erase(mp[find(j)].find(tv));
}
tmp.clear();
// printf("%d\n", mp.size());
for (auto &[j, tv] : vs[i])
{
mp[find(j)].insert(tv);
}
for (auto &[k, v] : mp)
{
if (!v.size())
continue;
merge(k, i);
res += *v.begin();
}
for (auto &[k, v] : mp)
{
if (!v.size())
continue;
if (v.size() > tmp[find(k)].size())
{
tmp[find(k)].swap(v);
}
for (auto &j : v)
tmp[find(k)].insert(j);
}
mp.swap(tmp);
}
if (siz[find(1)] != n + q)
return puts("-1"), 0;
printf("%lld\n", res);
}

浙公网安备 33010602011771号