一些废话
呃,无语,很是无语,对于 题把求最大值看成求最小值,而导致 了两发这档子事,我都不愿意承认是我干的。可问题是,这是怎么通过样例的,四五个样例愣是没有一个把你拦下来是吧。这次打的一般般吧,不算很好,但没有差的离谱,算是稳定失常。 题想出来了我还是蛮高兴的,我当时以为要一直卡死的,但当时,突然,灵光一现,那一刻我其实还没有想通,但是有种感觉告诉我,我知道了,我马上就要想出来了,就是那种怦然心动的感觉,类似于。但之后又卡 了,难受,这个 给我的感觉就是不是做不出来,但就是差临门一脚,但对于这种赛制来说,只有 和 的区别,那就是等同于没做出来。呃,这个 ,我补了之后发现他大可跟 换个位置, 的难度全在读题理解上了
A. Iskander and Drawings
题意
给出一个由 ‘*’ 和 ‘#’ 构成的字符串,问由连续的 ‘#’ 构成的最长长度
思路
直接遍历,找最长的连续 ‘#’ 的长度即可
代码
#include <bits/stdc++.h>
using namespace std;
#define lowbit(x) (x & (-x))
#define endl '\n'
typedef long long LL;
#define PLL pair<LL, LL>
const LL MOD = 998244353;
const LL MAX = 1e6 + 100;
const LL INF = 0x3f3f3f3f3f3f3f;
void solve() {
int n;
cin >> n;
string s;
cin >> s;
int ans = 0, cnt = 0;
for (char c : s) {
if (c == '*') {
cnt = 0;
}
else {
++cnt;
}
ans = max(ans, (cnt + 1) / 2);
}
cout << ans << endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
LL T;
cin >> T;
while (T--) {
solve();
}
return 0;
}
B. Nikita and Books
题意
给出一个数组 ,若 ,可进行以下操作:,问,给出的数组 能否最终成为一个严格递增数组
思路
最可能使其成为严格递增数组的模样为
若 , 则将多余的数保存起来,留给后面用
若 , 则将之前保存的数分配 这么多给 ,若将全部的之前保存的数都给了 ,但他仍小于 ,则输出
代码
#include <bits/stdc++.h>
using namespace std;
#define lowbit(x) (x & (-x))
#define endl '\n'
typedef long long LL;
#define PLL pair<LL, LL>
const LL MOD = 998244353;
const LL MAX = 1e6 + 100;
const LL INF = 0x3f3f3f3f3f3f3f;
void solve() {
LL n;
cin >> n;
vector<LL> a(n + 1);
LL tmp = 0;
for (LL i = 1; i <= n; ++i) {
cin >> a[i];
}
for (LL i = 1; i <= n; ++i) {
if (a[i] > i) {
tmp += a[i] - i;
}
else {
LL p = min(tmp, abs(i - a[i]));
a[i] += p;
tmp -= p;
if (a[i] < i) {
cout << "NO" << endl;
return;
}
}
}
cout << "YES" << endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
LL T;
cin >> T;
while (T--) {
solve();
}
return 0;
}
C. Stepan and Permutation
题意
给出一个长度为 的排列 和两个整数 ,若下标 满足以下任意一个条件,则可以交换元素 :
问,是否可以使用任意数量的此操作对此排列进行排序,最后形成一个递增数列
思路(裴蜀定理)
当时我是猜的,去找 ,若需要交换的两个下标 能够被 整除,则能够排序,否则不能。后来我查了一下,需要用到裴蜀定理( 定理),以下是一个简要证明:
以上这步可以多说一下,举个例子帮助理解 :
可以看出, ,当然,这只是举个例子帮助理解,并不是在严格证明
以上这步推导也可以多说一下:
要整除 , 要整除 , 一定会整除 ,若有非 的部分存在于 的因数中,就比如 中有 的话, 可以整除 ,但是无法整除 ,假设 硬是要也可以整除 ,则说明 中也有 ,既然 中都有 ,则 中不可能没有 ,所以,依然是 中硬多出来的 除以 被带出来的
用人话说,裴蜀定理就是给出两个数 , 他们两的线性组合一定是他们两的最大公因数的倍数
代码
#include <bits/stdc++.h>
using namespace std;
#define lowbit(x) (x & (-x))
#define endl '\n'
typedef long long LL;
#define PLL pair<LL, LL>
const LL MOD = 998244353;
const LL MAX = 1e6 + 100;
const LL INF = 0x3f3f3f3f3f3f3f;
void solve() {
LL n, x, y;
cin >> n >> x >> y;
LL z = gcd(x, y);
vector<LL> a(n + 1);
for (LL i = 1; i <= n; ++i) {
cin >> a[i];
}
for (LL i = 1; i <= n; ++i) {
if (a[i] != i)
{
LL tmp = abs(a[i] - i);
if (tmp % z != 0) {
cout << "NO" << endl;
return;
}
}
}
cout << "YES" << endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
LL T;
cin >> T;
while (T--) {
solve();
}
return 0;
}
D. Yaroslav and Productivity
题意
给出两个数组分别为 ,, 代表的含义是将 这个区间中的数字都乘以 ,对于每个 你可以选或不选,问,最后数组 的和可能的最大值
思路
将 进行排序,可以看出 将 划分划分成了 段,前 中的每一段你都可以选择反转或不反转,设 为 的前缀和数组,若 ,则不反转,若 ,则反转,统一起来,即为 。每一部分都会选择反转()或不反转(),最后,对于 数组来说,则为 0 1 1 1 0 0 1 0 1 1 0 0 1 的形式,我们一定可以用相应的 数组实现该 构成的数组。

我们只需要知道一定是可以实现的即可,因为真正写的代码只需要获取每一部分(除第 部分)的绝对值,因为你无论是变还是不变,加在答案中的都是绝对值
代码
#include <bits/stdc++.h>
using namespace std;
#define lowbit(x) (x & (-x))
#define endl '\n'
typedef long long LL;
#define PLL pair<LL, LL>
const LL MOD = 998244353;
const LL MAX = 1e6 + 100;
const LL INF = 0x3f3f3f3f3f3f3f;
void solve() {
LL n, m;
cin >> n >> m;
vector<LL> s(n + 1, 0), b(m + 1, 0);
LL ans = 0;
for (LL i = 1; i <= n; ++i) {
cin >> s[i];
s[i] += s[i - 1];
}
for (LL i = 1; i <= m; ++i) {
cin >> b[i];
}
sort(b.begin() + 1, b.end());
for (LL i = 1; i <= m; ++i) {
LL l = b[i - 1];
LL r = b[i];
ans += abs(s[r] - s[l]);
}
ans += s[n] - s[b.back()];
cout << ans << endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
LL T;
cin >> T;
while (T--) {
solve();
}
return 0;
}
E. Masha and the Garland
题意
给出一个由 0 1 构成的字符串,你可以进行以下操作:
- 选取 , 使得该区间中的字符全部翻转
给出 次询问,每次给出一个区间 和一个整数 ,问能否在 此操作中使得 区间成为一个 01 严格交错的区间(比如 0101010101… or 1010101010…)
思路
实在没看懂官方题解,我就讲我那笨笨的思路好吧
对于给出的 01 字符串来说,他想变成的样子无非是 0101010… 或者 10101010… 我们就先讨论其中第一个
将原串与目标串进行比较,相等则为 0,不等则为 1,则出现一个新的数组 r,我们想让原串变成目标串,等价于让 r 全部变成 0 ,所进行的最少操作数。将连续的 1 看成一组,答案等于 1 的组数和。比如 r1 : 0 0 1 1 1 0 1 1 0 0 有两组 1 ,一种方案是 [3, 5], [7, 8],令一种方案是 [3, 8], [6, 6],操作数都为 2。我想说明的是一组一组地合并 1 一定是不劣的,因为无论你按身什么顺序合并,操作一次,最多只能减少一组 1,这个是跟操作顺序无关的。所以答案为 1 的组数。但是我们发现,它还可以变成 1010101… ,这样数组 r2 就会变成 r1 的翻转数组:1 1 0 0 0 1 0 0 1 1,这样 1 的组数为 3,在这两者中取个 min, 即为最终答案
但是我们发现可以整合一下,把连续的一块看成一组,答案为 ,但是块是不好标记的,分界线却是好标记的,我们可以转换成用分界点来推出答案
当 时,标记 , 用前缀和数组 维护,答案为
这道题的确不是我喜欢的类型,我补完了都觉得跟没补没啥区别,既没学到什么,也没什么成就感,就他的每一步都透露出一丝不可言说的意味
官方思路
问了官老师,以下是他解释的官方思路,这玩意儿不比官方解释的好吗
题解的意思是:
第一段:将原字符串 s 按最大可能的”美丽串“划分方式,分割出所有美丽串
比如:s = 1000110100101
那么第一段会将 s 划分为:10-0-01-1010-0101,这 5 个 block
第二段:
如果我们翻转一段”内部 block“,那么 block 的数量就会减少 2,比如:
10-0-01-1010-0101
10[edge block]-0[internal block]-01[internal block]-1010[internal block]-0101[edge block]
如果我翻转了第 2 个 block ”0“,那么 block 的划分就会变成:
10101-1010-0101
因此 block 的个数会从 5 变为 3,减少了 2
如果我们翻转一段”边缘 block“,那么 block 的个数就会减少 1,比如:
10-0-01-1010-0101
翻转第 1 个 block ”10“,block 划分会变为:
010-01-1010-0101
block 个数从 5 变为 4,减少了 2
所以如果有内部 block 时选择边缘 block 翻转是更差的,优先选择内部 block 进行翻转
第三段:因此,最优策略是不断翻转第二个 block(如果存在内部 block,那么第 2 个 block 一定是内部 block,如果不存在内部 block,那么第 2 个 block 一定是边缘 block,所以只要翻转第二个 block,就自然保证了优先选择内部 block 进行翻转,其次选择边缘 block 翻转)
每选择一次第二个 block,block 数都会减少 2(这句话并不严谨,因为如果第二个 block 是边缘 block,那么翻转后 block 数只会减少 1)
所以最少操作次数是 cnt/2 下取整(虽然上一句话不严谨,但这个结论是对的)
第四段:用前缀和快速计算区间 block 数,算出最少翻转次数,与 k 进行大小比较
代码
#include <bits/stdc++.h>
using namespace std;
#define lowbit(x) (x & (-x))
#define endl '\n'
typedef long long LL;
#define PLL pair<LL, LL>
const LL MOD = 998244353;
const LL MAX = 1e6 + 100;
const LL INF = 0x3f3f3f3f3f3f3f;
void solve()
{
int n, q;
cin >> n >> q;
string str, t = " ";
cin >> str;
str = ' ' + str;
for (int i = 1; i <= n; ++i)
{
t += char(i % 2) + '0';
}
string r = "#";
for (int i = 1; i <= n; ++i)
{
r += char(str[i] == t[i] ? 0 : 1) + '0';
}
vector<int> s(n + 1);
for (int i = 1; i <= n; ++i)
{
s[i] = s[i - 1] + (r[i] != r[i - 1]);
}
while (q--)
{
int l, r, k;
cin >> l >> r >> k;
int ans = (s[r] - s[l] + 1) / 2;
if (k >= ans)
{
cout << "YES" << endl;
}
else
{
cout << "NO" << endl;
}
}
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
LL T;
cin >> T;
while (T--)
{
solve();
}
return 0;
}F. Anya Loves Trees!
题意
给出一棵 个节点的树, 第 个节点有其对应的价值 ,对于一个节点来说,他的孩子节点可以做无数次循环左移(从第二个点到后面每个点都往左移一个,第一个点移到最后),问,最后将所有叶子结点按从左到右的顺序,他们对应的 能否成为一个单调递增序列(保证每个 都不相同)
思路
说真的,我当时读这题的时候就火大,感觉他说的不清不楚,但没办法,我好像也说不清楚,我只能建议你多读几遍,读清楚了,后面的才好理解。
对于节点 来说,假设他的孩子可能是一段一段的,最小值 和最大值 ,找到在 中最小的那个,因为循环左移后,左端点最小值一定是第一个,然后再进行遍历,若出现第 个的左端点小于第 个的右端点,则无论如何移动,都无法得到单调递增序列,直接输出 , 退出递归。若成功遍历完,则对于 来说只需保留最小的左端点和最大的右端点,然后重复操作
对于一层来说时间复杂度是 ,共有 层,所以总的时间复杂度是
代码
#include <bits/stdc++.h>
using namespace std;
#define lowbit(x) (x & (-x))
#define endl '\n'
typedef long long LL;
#define PLL pair<LL, LL>
const LL MOD = 998244353;
const LL MAX = 1e6 + 100;
const LL INF = 0x3f3f3f3f3f3f3f;
struct Node {
LL l, r;
};
void solve() {
LL n;
cin >> n;
vector<vector<LL>> G(n + 1);
vector<LL> p(n + 1);
for (LL i = 2; i <= n; ++i) {
LL u;
cin >> u;
G[u].push_back(i);
}
for (LL i = i = 1; i <= n; ++i) {
cin >> p[i];
}
vector<Node> t(n + 1);
auto dfs = [&](auto &&dfs, LL u) -> bool
{
vector<Node> s;
for (LL v : G[u])
{
if(!dfs(dfs, v))
return false;
s.push_back({t[v]});
}
LL l = INF, r = -1, L;
LL m = s.size();
for (LL i = 0;i < m; ++i) {
if (s[i].l < l) {
L = i;
}
l = min(l, s[i].l);
r = max(r, s[i].r);
}
if (s.size() == 0)
{
t[u].l = p[u];
t[u].r = p[u];
}
else
{
t[u].l = l;
t[u].r = r;
}
for (LL i = 1; i < m; ++i)
{
if (s[(i + L) % m].l < s[(i - 1 + L) % m].r)
{
return false;
}
}
return true;
};
if (dfs(dfs, 1)) {
cout << "YES" << endl;
}
else {
cout << "NO" << endl;
}
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
LL T;
cin >> T;
while (T--) {
solve();
}
return 0;
}