Codeforces Round #825 (Div. 2) A-D
A
题解
知识点:贪心。
考虑两种方法:
- 所有不同的位置使用操作1变成相同
- 使用操作1将两串01数量相同,然后使用1次操作2
取其中最小的即可。
时间复杂度 \(O(n)\)
空间复杂度 \(O(n)\)
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
int a[107], b[107];
bool solve() {
int n;
cin >> n;
for (int i = 1;i <= n;i++) cin >> a[i];
for (int i = 1;i <= n;i++) cin >> b[i];
int cnt1 = 0;
int cnt2 = 0;
for (int i = 1;i <= n;i++) {
cnt1 += a[i] != b[i];
cnt2 += a[i] - b[i];
}
cout << min(cnt1, abs(cnt2) + 1) << '\n';
return true;
}
int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t = 1;
cin >> t;
while (t--) {
if (!solve()) cout << -1 << '\n';
}
return 0;
}
B
题解
知识点:数论,贪心。
充要条件:存在 \(b\) ,当且仅当 \(gcd(a[i-1],a[i+1]) | a[i]\) 。
显然,如果存在 \(b\) 那么 \(b[i]\) 一定会包括 \(a[i-1]\) 和 \(a[i]\) 的因子,而 \(a[i] = gcd(b[i],b[i+1])\) 一定能被 \(gcd(a[i-1],a[i])\) 整除。
若 \(gcd(a[i-1],a[i+1]) | a[i]\) ,那么构造 \(b[i] = lcm(a[i],a[i-1])\) ,则:
注意到,因为 \(gcd(a[i-1],a[i+1])|a[i]\) ,因此 \(gcd(a[i-1],a[i])\) 一定包含 \(gcd(a[i-1],a[i+1])\) ,同理 \(gcd(a[i],a[i+1])\) 也是,因此 \(gcd\bigg(\dfrac{a[i-1]}{gcd(a[i-1],a[i])},\dfrac{a[i+1]}{gcd(a[i],a[i+1])}\bigg) = 1\) ,所以 \(gcd(b[i],b[i+1]) = a[i]\) ,存在 \(b\) 。
时间复杂度 \(O(n)\)
空间复杂度 \(O(n)\)
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
int a[100007];
bool solve() {
int n;
cin >> n;
for (int i = 1;i <= n;i++) cin >> a[i];
for (int i = 2;i <= n - 1;i++) {
if (a[i] % __gcd(a[i - 1], a[i + 1])) return false;
}
cout << "YES" << '\n';
return true;
}
int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t = 1;
cin >> t;
while (t--) {
if (!solve()) cout << "NO" << '\n';
}
return 0;
}
C1
题解
知识点:双指针,枚举。
对于区间内的一个位置 \(i\) ,当左端点 \(l\) 满足 \(i-a[i]+1\leq l\) 时,\(i\) 才能可能存在于 \(l\) 开始的区间。
同时,注意到区间端点具有单调性,即 \([l,r]\) 是一个合法区间,那么 \([i,r],i\in[l,r]\) 都是合法区间,因此可以尺取法枚举左端点 \(l\) ,找到第一个不可行右端点 \(r\) ,就直接得到从 \(l\) 为左端点的区间个数 \(r-l\) 。
时间复杂度 \(O(n)\)
空间复杂度 \(O(n)\)
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
int a[200007];
bool solve() {
int n;
cin >> n;
for (int i = 1;i <= n;i++) cin >> a[i];
int l = 1, r = 1;
ll ans = 0;
while (l <= n) {
while (r <= n) {
if (r + 1 - a[r] > l) break;
r++;
}
ans += r - l;
l++;
}
cout << ans << '\n';
return true;
}
int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t = 1;
cin >> t;
while (t--) {
if (!solve()) cout << -1 << '\n';
}
return 0;
}
C2
题解
知识点:前缀和,双指针,枚举。
此题能用线段树二分做,但实际上二分查找右端点没有必要,可以尺取法中同时预处理出来。
先考虑修改 \(a[p]\) 到 \(x\) 会改变什么。如果 \(x > a[p]\) ,那么一些原本右端点被 \(p\) 阻挡的区间现在可能跨越 \(p\) 再往后走一段;如果 \(x<a[p]\) ,那么原本一些跨越了 \(p\) 的区间右端点现在可能会被 \(p\) 阻挡(\(\geq p\) 的不受影响接下来不讨论)。
明确了修改 \(a[p]\) 会导致的变化,那就开始具体讨论:
-
\(x < a[p]\) ,我们首先需要知道跨越了 \(p\) 的具体区间是什么才能进行下一步操作。因为区间端点是单调的,我们只需要知道第一个右端点可以跨越 \(p\) 的左端点即可,那么到 \(p-1\) 为止的左端点都是可以跨越 \(p\) 的。
我们在尺取法中扩展右端点时,用 \(Lr[r]\) 记录以 \(r\) 为右端点的左端点,由于每个 \(r\) 只会记录一次,那么 \(Lr[r]\) 一定代表第一次以 \(r\) 为右端点可行的左端点 \(l\) ,所以 \(Lr[p]\) 即区间右端点能跨越 \(p\) 的第一个左端点。
接下来要考虑 \(a[p]\) 修改成 \(x\) 之后影响了左端点 \([Lr[p],p-1]\) 的哪一部分。显然, \(x\) 在位置 \(p\) 作为右端点时,左端点的可能范围是 \([p-x+1,p-1]\) 。那么只有在 \(p-x+1 > Lr[p]\) 时,才会使 \([Lr[p],p-x]\) 这些原本能跨越 \(p\) 的左端点的右端点被 \(p\) 阻挡,否则不会影响答案。
因此,我们设前缀和 \(pre[i]\) 代表 \([1,i]\) 的点作为左端点产生的合法区间个数。我们先减去 \([Lr[p],p-x]\) 这部分左端点原本提供的区间个数,再加上新的个数,即 \(\sum_{i = Lr[p]}^{p-x} p-i = \dfrac{(p-Lr[p]+x)(p-x-Lr[p]+1)}{2}\) ,每个左端点 \(i\) 被 \(p\) 阻挡提供 \(p-i\) 个区间。所以最终答案为 \(pre[n] - (pre[p-x]-pre[Lr[p]-1]) + \dfrac{(p-Lr[p]+x)(p-x-Lr[p]+1)}{2}\) 。
-
\(x>a[p]\) ,同样我们先确定右端点被 \(p\) 阻挡的左端点区间。我们只需要在每个左端点 \(l\) 扩展不了时,记录一下此时的 \(r\) ,即 \(Ll[r] = l\) 来表示被 \(r\) 阻挡的左端点,同时对于每个 \(r\) 只记录一次,因此 \(Ll[r]\) 就代表第一个被 \(r\) 阻挡的左端点。
在上文提到 \(Lr[r]\) 代表第一个可以跨越 \(r\) 的左端点,那 \(Lr[r]-1\) 就是最后一个被 \(r\) 阻挡的左端点。于是, \([Ll[p],Lr[p]-1]\) 就是被 \(p\) 阻挡的左端点区间。
注意到,可能出现不存在左端点会被 \(p\) 阻挡,这时候 \(Ll[p]\) 应为初始化的值 \(0\) ,答案不变。否则,被 \(p\) 阻挡的左端点区间必然存在。
同时,如果存在这样的区间,那么 \(p-a[p]+1\) 必然等于 \(Lr[p]\) 。因此如果 \(x<a[p]\) ,那么 \(p-x+1\) 一定小于 \(Lr[p]\) ,一定会改变答案。 于是,需要修改答案的左端点区间就是 \([\max(Ll[p],p-x+1),Lr[p]-1]\) 。
最后我们还需要知道,这些左端点跨越了 \(p\) 之后的右端点最多到哪。在一开始的尺取法时,我们用 \(rr\) 来记录,当 \(l\) 被 \(r\) 阻挡时,跨越 \(r\) 后又会被阻挡的位置,那么 \(rr-l\) 即 \(l\) 跨越 \(r\) 后的合法区间的新个数。
我们同样用前缀和记录一下,\(skpre[i]\) 代表 \([1,i]\) 的区间的左端点,可以跨越一次阻挡自己的位置后的合法区间个数。我们先减去 \([\max(Ll[p],p-x+1),Lr[p]-1]\) 的原本的答案 \(pre[Lr[p]-1] - pre[\max(Ll[p],p-x+1)-1]\) ,再加上新的个数 \(skpre[Lr[p]-1] - skpre[\max(Ll[p],p-x+1)-1]\) 。 所以最终答案为 \(pre[n] - (pre[Lr[p]-1] - pre[\max(Ll[p],p-x+1)-1]) + (skpre[Lr[p]-1] - skpre[\max(Ll[p],p-x+1)-1])\)
时间复杂度 \(O(n+q)\)
空间复杂度 \(O(n)\)
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
int a[200007];
int Ll[200007], Lr[200007];
ll pre[200007], skpre[200007];
int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n;
cin >> n;
for (int i = 1;i <= n;i++) cin >> a[i];
int l = 1, r = 1, rr = 1;//l为目前起点,r为以l为左端点的第一个不行的点,rr为跨越障碍点后第一个不行的点
while (l <= n) {
while (r <= n && r - a[r] + 1 <= l) Lr[r++] = l;//Lr[r]为r左边第一个可行l,即左边最后一个以r为障碍的点+1
rr = min(max(rr, r + 1), n + 1);
while (rr <= n && rr - a[rr] + 1 <= l) rr++;
if (!Ll[r]) Ll[r] = l;//Ll[r]为r左边第一个不可行l
pre[l] = pre[l - 1] + r - l;
skpre[l] = skpre[l - 1] + rr - l;
l++;
}
int q;
cin >> q;
while (q--) {
ll ans = pre[n];
int p, x;
cin >> p >> x;
if (x < a[p] && p - x + 1 > Lr[p]) {
ans -= pre[p - x] - pre[Lr[p] - 1];
ans += 1LL * (x + p - Lr[p]) * (p - x - Lr[p] + 1) / 2;
}
else if (x > a[p] && Ll[p]) {
ans -= pre[Lr[p] - 1] - pre[max(p - x + 1, Ll[p]) - 1];
ans += skpre[Lr[p] - 1] - skpre[max(p - x + 1, Ll[p]) - 1];
}
cout << ans << '\n';
}
return 0;
}
D
题解
知识点:构造。
显然,\(1\) 或 \(0\) 的个数为奇数时一定不可能。
猜想,\(1\) 和 \(0\) 个数都为偶数时,一定能够造出。考虑两两构造成 \(s[i] = s[i+1]\) 的形式,如此只要取 \(i = 2k-1\) 即可。
当 \(s[i] = s[i+1]\) 时符合构造不需要修改,当 \(s[i] \neq s[i+1]\) 时考虑如下修改。
不符合我们构造形式的 \(01\) 组合一定是成对出现,比如 01
出现,因为 \(0\) 和 \(1\) 的个数都为偶数,那么一定会再出现一次 \(s[i] \neq s[i+1]\) 的组合来使个数为偶数。
我们考虑对第一个组合取出一个 \(0\) ,那么剩下一个 \(1\) ,需要从下一个组合取出一个 \(1\) 给这一组即可,那么下一组就会剩下一个 \(0\) ,再从下下一组取出一个 \(0\) ,以此类推。因为成对出现,所以取到最后一定取的是 \(1\) ,剩下一个 \(0\) ,那么把第一组取的 \(0\) 填上即可。
我们注意到上面取的过程,实际上就是右边一组往左边一组传递一个数字,第一组给最后一组传递一个数字,也就是题目给出的一次操作,于是就构造成功了。
时间复杂度 \(O(n)\)
空间复杂度 \(O(n)\)
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
bool solve() {
int n;
string s;
cin >> n >> s;
if (count(s.begin(), s.end(), '1') & 1) return false;
s = '?' + s;
bool cur = 0;//cur代表上一组不同的缺的数字
vector<int> b;
for (int i = 1;i <= 2 * n;i += 2) {
if (s[i] != s[i + 1]) {
if (s[i] - '0' == cur) b.push_back(i), cur ^= 1;//取出上一组需要的数字,然后更换cur,往复循环
else b.push_back(i + 1), cur ^= 1;
}
}
cout << b.size() << ' ';
for (auto i : b) cout << i << ' ';
cout << '\n';
for (int i = 1;i <= 2 * n;i += 2) cout << i << ' ';
cout << '\n';
return true;
}
int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t = 1;
cin >> t;
while (t--) {
if (!solve()) cout << -1 << '\n';
}
return 0;
}
本文来自博客园,作者:空白菌,转载请注明原文链接:https://www.cnblogs.com/BlankYang/p/16796363.html