A. Dreamoon and Ranking Collection
把 $n$ 个名次标记一下, 然后从 $1$ 开始往后枚举,如果枚举到了一个没有出现过的名次且 $x$ 次新增机会有剩余的话,就将其中一次机会的名次确定为该名次,然后继续向后枚举,直到 $x$ 次机会全部用完,最终枚举到的数 $-1$ 即为答案。由于数据范围较小,可以直接这样找答案。
#include <bits/stdc++.h>
using namespace std;
const int N = 210;
int n, k;
bool vis[N];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int T;
cin >> T;
while (T--) {
memset(vis, false, sizeof vis);
cin >> n >> k;
for (int i = 1; i <= n; i++) {
int x;
cin >> x;
vis[x] = true;
}
int res = 1;
while (vis[res])
res++;
while (k) {
vis[res] = true;
k--;
while (vis[res])
res++;
}
cout << res - 1 << endl;
}
return 0;
}
B. Dreamoon Likes Permutations
不难发现,分割成两部分之后,每一部分必定是从 $1$ 开始的连续的整数,且同一部分中不会有相同的数字。
那么,我们可以从左向右枚举,直到出现左侧相同的数字,由此为分割线,将原数组分成左右两部分,然后判断左右两部分是否分别满足上述条件;同理,从右向左开始也需要进行同样的一次操作。
能够得到多少根合法的分割线就有几种分割方式,任意次序输出即可。
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 2e5 + 5;
int n, a[N];
bool vis[N];
vector<PII> res;
bool check(int n)
{
for (int i = 1; i <= n; i++)
if (!vis[i])
return false;
return true;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int T;
cin >> T;
while (T--) {
res.clear();
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
vis[i] = false;
}
int bound = 1;
while (!vis[a[bound]])
vis[a[bound++]] = true;
if (check(bound - 1)) {
for (int i = 1; i < bound; i++)
vis[i] = false;
int l = bound - 1;
while (bound <= n && !vis[a[bound]])
vis[a[bound++]] = true;
if (bound > n && check(n - l))
res.push_back({ l, n - l });
}
for (int i = 1; i <= n; i++)
vis[i] = false;
bound = n;
while (!vis[a[bound]])
vis[a[bound--]] = true;
if (check(n - bound)) {
for (int i = 1; i <= n - bound; i++)
vis[i] = false;
int r = n - bound;
while (bound && !vis[a[bound]])
vis[a[bound--]] = true;
if (!bound && check(n - r) && (res.empty() || res.back() != make_pair(n - r, r)))
res.push_back({ n - r, r });
}
cout << res.size() << endl;
for (auto t : res)
cout << t.first << ' ' << t.second << endl;
}
return 0;
}
C. Dreamoon Likes Coloring
首先能够判断的是,如果 $\sum_{i = 1}^{m}{l_i}$ 都比 $n$ 要小的话,无论如何都不能涂满 $n$ 个格子,说明无解。
否则的话,我们可以首先想到以下贪心思路:第一块选择从下标 $1$ 开始,第二块选择从下标 $2$ 开始,…,以此类推,可以保证每种颜色都至少有其起始位置是涂了该种颜色且不会被覆盖的,但是这样的话又会出现一个问题,可能又不能涂满 $n$ 个格子了。
假设当前需要选择第 $i$ 块的起点,此时如果出现这种情况:从 $i$ 开始的后缀的块的总长度加上前 $i - 1$ 块放置之后去掉重叠部分之后的长度之和不足 $n$.
那么,第 $i - 1$ 块的起点就需要向右移动,如果仍然出现上述情况,则需要将 $i - 2$ 块的起点向右移动,…,以此类推,这个过程是一定会停止的,因为开始我们就特判了所有块的长度之和小于 $n$ 为无解。
不难总结出来,每一块的起始位置只有两种选择,如果当前需要确定第 $i$ 块的起始位置,则只需要在 $i$ 与 $ 从 i 开始的后缀总长度以 n 为终点,向前延伸到的起点位置 $ 中选择较大者作为第 $i$ 块的位置即可。
为方便表述,我们将第二种选择的位置记为 $k$.
- 当 $i = k$ 时,完美放置,不需要证明。
- 当 $i < k$ 时,根据上文的证明,在前 $i - 1$ 块都以这种方式放置的前提下,第 $i - 1$ 块的终点必定会 $ \geq k - 1$,即:不会出现中间有格子未被涂色的情况。
- 当 $i > k$ 时,由于后续的格子放置是可以重叠的,所以第 $i$ 块的起点可以为 $i$.
至此,起点的选择就告一段落,那么这种起点选择的方式,每一种颜色块的终点是否一定合法?
由于只能在 $n$ 以内涂色,所以如果终点超过了 $n$,则应当是无解,因此,对于每种颜色块的起点确定之后,需要判断一下其终点是否会超过 $n$,如果超过了则直接输出无解。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e5 + 5;
int n, m;
int l[N], p[N];
LL suf[N];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= m; i++)
cin >> l[i];
for (int i = m; i; i--)
suf[i] = suf[i + 1] + l[i];
if (suf[1] < n) {
cout << -1 << endl;
return 0;
}
for (int i = 1; i <= m; i++) {
if (i + l[i] - 1 > n) {
cout << -1 << endl;
return 0;
}
p[i] = max(1ll * i, n - suf[i] + 1);
}
for (int i = 1; i <= m; i++)
cout << p[i] << ' ';
cout << endl;
return 0;
}
D. Dreamoon Likes Sequences
不难发现,其实序列 $b$ 就是序列 $a$ 的前缀异或,而要使 $a$ 的前缀异或仍然能够保持严格单调上升的话,一个最直观的想法就是让 $a$ 序列依次递增着出现二进制位的 $1$,例如:$a_1$ 在第 $0$ 个二进制位有 $1$,$a_2$ 在第 $1$ 个二进制位有 $1$,以此类推,当然出现的二进制位可以不连续。
此时,$a$ 的前缀异或也必定在对应的最高出现 $1$ 的二进制位上也出现 $1$,达成了依旧严格单调上升的要求。
那么,是否存在这样一种情况,$a_i, a_j (i < j)$ 的最高为 $1$ 的二进制位相同,但是低位的存在使得 $a_i < a_j$,前缀异或之后使 $b_i < b_j?$
假设 $i$ 之前的数是以上文的方式构造的,只有 $i, j$ 是该特殊情况,不妨设 $a_i, a_j$ 的最高为 $1$ 的二进制位是第 $k$ 位,那么,$b_i$ 的最高位 $1$ 的二进制位也是第 $k$ 位,此时 $b_j = b_i \oplus a_j$ 的第 $k$ 位二进制位就会变成 $0$,且高于 $k$ 的二进制位不可能出现 $1$,所以 $b_i > b_j$,不满足题意要求。
因此,对于一个二进制位 $i$ 而言,可以分成两种情况讨论:
- 这一位不为 $1$,此时就一种选择,即:填上 $0$.
- 这一位为 $1$,那么这一位填上 $1$ 之后,低于 $i$ 的二进制位可以任意选择是否填 $1$,即:$2 ^ {i + 1} - 1 - 2 ^ i + 1$ 种选择,其中 $2 ^ {i + 1} - 1$ 是该位为 $1$ 的最大值,$2 ^ {i}$ 是该位为 $1$ 的最小值,当然,如果 $d < 2^{i + 1} - 1$,那么最大值就应该为 $d$,且在计算完该二进制位后即可跳出循环。
所以,对于每一个二进制位,都有 $min(d, 2 ^{i + 1} - 1) - 2^i + 2$ 种选择,直接将每个二进制位的选择数量相乘,由于其中包含每一位都不选择的情况,这时不满足题意的,所以最后还需要 $-1$ 才是最终答案。
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int T;
cin >> T;
while (T--) {
int d, mod;
cin >> d >> mod;
int res = 1;
for (int i = 0; d >= 1 << i; i++)
res = 1ll * res * (min(d, (1 << i + 1) - 1) - (1 << i) + 2) % mod;
res = ((res - 1) % mod + mod) % mod;
cout << res << endl;
}
return 0;
}
E. Drazil Likes Heap
由于维护成大根堆的数组是降序排列的,所以,如果能删除数组中下标为 $1$ 的数,就应该将其删除,那么,什么时候不能删下标为 $1$ 的数呢?
考虑一下特殊情况,如果删除下标为 $1$ 的数,而且最终补在这个位置上的数,都是从同一条链上来的,那么,这就会导致最终的二叉树中有一条链的深度很浅。
但是,题目要求最终剩下的 $2^g - 1$ 个数要依次排列在数组中,即:剩下的数要形成满二叉树的形式。
这就说明,如果能删下标为 $1$ 的数,那么,在删除这个数之后,不会导致二叉树的最低高度 $< g$,否则的话,就不能再删除 $1$ 这个位置上的数,以此类推,依次判断 $2, 3, …$ 位置上的数能否被删除,那么,在遍历完所有的位置之后,我们就能得到一棵剩下 $2^g - 1$ 个节点的满二叉树,且剩余的节点权值之和最小。
这个判断操作类似于删除操作,可以用 $DFS$ 在 $log$ 的时间复杂度内判定。
由于我们在判定成功某个节点并删除之后,需要继续判定这个节点,那么这个 $while$ 循环是否会导致 $TLE?$
极端情况下,我们在 $1$ 这个位置就能删除高度 $>g$ 的所有节点,那么此时的时间复杂度为 $O((2^h - 2 ^ g) \cdot logn)$,接下来在 $1$ ~ $2^g - 1$ 这些位置,每次只需要做 $log$ 的失败判定操作就能跳出 $while$,所以该解法的总操作的时间复杂度完全能够通过此题。
我也不知道自己为什么拖了这么久才写这篇题解,其实自己已经拖更好几场比赛了,不知道是该跳过这几场写最新场次的题解还是应该一场一场慢慢补全
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1 << 20;
int h, g;
LL sum;
int a[N], d[N];
vector<int> q;
void remove(int p)
{
if (d[p] == h) {
a[p] = 0;
return;
}
int lp = p << 1, rp = lp | 1;
if (!a[lp] && !a[rp])
a[p] = 0;
else if (a[lp] > a[rp]) {
a[p] = a[lp];
remove(lp);
} else {
a[p] = a[rp];
remove(rp);
}
return;
}
bool dfs(int p)
{
if (d[p] > g)
return true;
int lp = p << 1, rp = lp | 1;
if (!a[lp] && !a[rp])
return false;
if (a[lp] > a[rp])
return dfs(lp);
return dfs(rp);
}
int main()
{
int T;
scanf("%d", &T);
while (T--) {
scanf("%d %d", &h, &g);
sum = 0;
for (int i = 1; i < 1 << h; i++) {
scanf("%d", a + i);
sum += a[i];
d[i] = d[i >> 1] + 1;
}
q.clear();
for (int i = 1; i < 1 << g; i++)
while (dfs(i)) {
q.push_back(i);
sum -= a[i];
remove(i);
}
printf("%lld\n", sum);
for (auto t : q)
printf("%d ", t);
printf("\n");
}
return 0;
}
图片太大且不清晰,建议删除哦~
跟我打两把saber我就删。
我太菜了,别虐我啊~~