AtCoder Grand Contest 003 解题报告
A-Wanna go back home
题目链接
题目简述
高桥君在一个平面上走路N次,且每次走路的方向已经确定,但是距离没有确定。
请确定每次走路的距离 (至少为 \(1\) ) ,使高桥君在这N次走路后可以回到 \((0,0)\)。
给你一个字符串 \(S\) ,由字符 N W S E 组成,表示向北/西/南/东行动,输出 Yes 表示高桥君能够回到 \((0,0)\),否则输出 No。
- \(1 \leq |S| \leq 1000\)
解题思路
非常简单,如果高桥君向某个方向行动,且不会向这个方向的反方向行动,则高桥君显然无法回到 \((0, 0)\),否则一定可以。
B - Simplified mahjong
题目链接
题目简述
给定 \(N\),有一组面值为 \(1 \to N\) 的牌,已知每种面值的个数 \(A_i\),卡牌 \(a\) , \(b\)可以组成一对 \((a, b)\) 当且仅当 \(|a面值 - b面值| \leq 1\) 。问最多能组多少对。
-
\(1 \leq N \leq 10^5\)
-
\(1\leq A_i \leq 10^9\)
解题思路
贪心,从小到大扫描,优先和面值相同的配对,如果剩下了一张无法配对就和面值大一的配对。
我这题一开始想错了,某个面值剩下一张的时候并不一定会和大一的面值配对,只有当面值大一的也剩下一张才配对。这种贪心显然没有上面提到的决策优。
考虑一组数据
3
3 2 3
如果使用错误的贪心,在面值为 \(1\) 的牌与面值为 \(2\) 的牌配对时,会造成一张面值为 \(1\) 的牌的浪费,而强行配对不会造成浪费,也不会使答案更劣。
C - BBuBBBlesort!
题目链接
题目简述
给定一个序列 \(A\),保证元素两两不同,你可以对这个序列做以下两种操作。
-
翻转相邻两个元素。
-
翻转相邻三个元素。
请用这两种操作将序列 \(A\) 变为升序,并使操作 \(1\) 的次数尽可能少。
输出操作 \(1\) 的最少次数。
-
\(1 \leq |A| \leq 10^5\)
-
\(1 \leq A_i \leq 10^9\)
解题思路
其实也不困难,很容易发现使用操作 \(2\) 不能够将原本在第奇数个位置的数换到第偶数个位置上,同样也不能把原本在第偶数个位置上的数换到第奇数个位置上。但是能够将奇数、偶数位置上的数分别任意排列。因此我们只要尽可能把本应该放在奇数/偶数位置上的数放到奇数/偶数位置上即可。
容易发现一次操作 \(1\) 最多能同时把两个数放回对应的奇数/偶数位置,并且由于我们可以用操作 \(2\) 将奇数/偶数位置上的数分别任意排列,因此我们一定能够做到用一次操作 \(1\) 复位两个数,故我们只要统计有多少个原本应该在奇数/偶数位置上的数没有被放在奇数/偶数位置上,然后除以二就是答案。
参考代码
#include <vector>
#include <cctype>
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
using LL = long long;
int n;
vector<int> nums;
int a[100010], b[100010];
int main(void)
{
cin >> n;
for (int i = 0; i < n; ++i) {
cin >> a[i];
nums.emplace_back(a[i]);
}
sort(nums.begin(), nums.end());
for (int i = 0; i < n; ++i)
a[i] = lower_bound(nums.begin(), nums.end(), a[i]) - nums.begin();
int cnt = 0;
for (int i = 0; i < n; ++i) {
if (i % 2 != a[i] % 2) ++cnt;
}
cout << cnt / 2 << endl;
return 0;
}
D - Anticube
题目链接
题目简述
给定 \(n\) 个数 \(s_i\),要求从中选出最多的数,满足任意两个数之积都不是完全立方数。
-
\(1 \leq n \leq 10^5\)
-
\(1 \leq s_i \leq 10^{10}\)
没写出来,似乎是一道阴间数学题。
E - Sequential operations on Sequence
题目链接
题目简述
一串数,初始为\(1 \to N\) ,现在给 \(Q\) 个操作,每次操作把数组长度变为 \(q_i\),新增的数为上一个操作后的数组的重复。问 \(Q\) 次操作后 \(1 \to N\) 每个数出现了多少次。
- \(1 \leq N \leq 10^5\)
- \(0 \leq Q \leq 10^5\)
- \(1 \leq q_i \leq 10^{18}\)
解题思路
今天的重头戏。
首先我们很容易想到,虽然这个数组初始为 \(1 \to N\),但是如果存在 \(q_i < N\),并且对于 \(\forall j < i\),\(q_j >= q_i\),就会导致前面的一系列操作都是不用管的,到了第 \(i\) 步这个数组一定会变成 \(1\to q_i\)。
这个结论其实没用,但是很有启发性。于是我们很快又可以发现另一个性质:对于一段连续的操作 \(q_i,\ i \in [l,r]\) 如果对于 \(\forall i \in [l, r],\ q_i >= q_r\) ,那么这一段连续的操作与操作 \(q_r\) 等效。
这个性质说明了如果一个操作小于前面的一系列操作,前面的一系列操作可以不做。
这个性质很有用,它说明我们实际要进行的操作 \(q_i\) 是单调递增的,也就是说我们不用管这个数组的缩短,只用计算它的伸长就好了。
我们把初始的 \(N\) 也看作是一次操作,用单调栈或者倒序处理就能维护出一个单调递增的操作序列 \(Q'\)。
接下来我们考虑这些伸长的操作。初始数组长度为 \(q_0\),我们现在要进行操作将其伸长到 \(q_1\),显然原始数组重复了 \(\lfloor \cfrac{q_1}{q_0} \rfloor\) 次,并且后接一段长度为 \(q_1 \bmod q_0\) 的数组。
如果我们用 \(a_0\) 来表示初始数组,\(b_0\) 表示后面接的数组,那么就有
然后就有
接下来得出
如是 \(|Q'|\) 遍,最终就有
看起来非常复杂,我们尝试从外往里做。
我们用数组 f 记录数组 \(a_i\) 出现的次数(也就是它前面的系数),显然我们可以一层一层把式子拆开,把系数乘起来。
然而我们发现这坨式子中的 \(b_i\) 简直丧心病狂,我们重点讨论如何计算它。
由于 \(b_i\) 是重复的产物(并且是不完整的一段),显然如果 \(b_i\) 的长度是 \(l_i\),那么它就会等于整个数组最前面的 \(l_i\) 个数。
又由于整个数组是重复的产物,所以整个数组最前面的 \(l_i\) 个数也是某个数组重复的产物。是哪个数组呢?我们二分查找第一个小于等于 $ l_i$ 的 \(q_j\),\(b_i\) 就是由 \(q_j\) 对应的数组(也就是 \(a_j\)) 重复产生的。于是 \(a_j\) 又多出现了 \(\lfloor \cfrac{l_i}{q_j}\rfloor\) 次,也即它的系数需要加上 \(\lfloor\cfrac{l_i}{q_j}\rfloor\) 。但是我们发现 \(b_i\) 的末尾应该也会剩下一个无法用完整的 \(a_j\) 表示的数组,我们用和处理 \(b_i\) 一样的方法递归处理即可。当这个数组的长度小于 \(n\) 时,假设它的长度时 \(m\),这个数组实际上就是 \(1 \to m\)。也就是说 \(1 \to m\) 又出现了 \(k\) 次(\(k\) 是 \(b_i\) 对应的系数),给区间 \([1,m]\) 整个加上 \(k\) 即可,区间加法可以用差分数组维护。
最后,除了一大堆 \(b\) 之外,整个式子还会剩下一个 \(a_0\)。但是 \(a_0\) 我们是已知的,就是 \(1 \to q_0\),我们乘上系数,给区间 \([1,q_0]\) 做区间加法,最后前缀和统计答案即可。
参考代码
#include <vector>
#include <cctype>
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <algorithm>
using namespace std;
using LL = long long;
const int N = 1e5 + 10, M = 17;
int n, q;
LL a[N];
LL f[N];
LL c[N];
LL minx[N];
vector<LL> len;
vector<LL> na;
void Solve(LL x, LL times)
{
if (x <= na[0]) {
c[0] += times;
c[x + 1] -= times;
}
else {
int t = upper_bound(na.begin(), na.end(), x) - na.begin() - 1;
f[t] += x / na[t] * times;
Solve(x % na[t], times);
}
}
int main(void)
{
int on;
cin >> n >> q;
on = n;
int start = 1;
for (int i = 1; i <= q; ++i) {
io >> a[i];
if (a[i] < n) {
n = a[i];
start = i + 1;
}
}
minx[q] = q;
for (int i = q - 1; i >= 1; --i) {
if (a[i] < a[minx[i + 1]])
minx[i] = i;
else minx[i] = minx[i + 1];
}
na.emplace_back(n);
for (int i = start, j; i <= q; i = j + 1) {
j = minx[i];
na.emplace_back(a[j]);
}
f[na.size() - 1] = 1;
for (int i = na.size() - 1; i >= 1; --i) {
f[i - 1] += na[i] / na[i - 1] * f[i];
Solve(na[i] % na[i - 1], f[i]);
}
c[0] += f[0]; c[n + 1] -= f[0];
for (int i = 1; i <= on; ++i) {
c[i] += c[i - 1];
cout << c[i] << endl;
}
return 0;
}
F - Fraction of Fractal
题目链接
没看,不会做

浙公网安备 33010602011771号