Codeforces Round 1049 (Div. 2)总结
A
不难发现我们可以通过任意方式使得所有的 \(1\) 都直接达到三个字母的里的最右端或者最左端,于是直接贪心,看看所有 \(1\) 应该在的地方有哪些是 \(0\) 即可。
B
数学题,根据题意,我们首先考虑 \(x \leq 9 \ and \ 8x \leq 9\) 的情况,不难发现可以得到如下式子:
然后考虑这个式子对更大的 \(x\) 的通用性,发现:
可以变化为:
这依旧符合题意,于是直接输出 \(8 \times x\) 即可。
C
挺好玩的,也是一种贪心,一开始我还以为是博弈,然后就被卡了。
事实上是个比较简单的贪心,不难发现,Bob 的每一轮行动都可以被 Alice 抵消掉并且给答案增加不必要的数值,所以 Bob 的最优策略就是直接在他的第一回合结束游戏。所以我们要考虑的就是 Alice 如何在第一回合里得到最大答案。
同时不难发现,如果交换的是同奇偶性位置的数字,贡献只会是位置差。
观察如下式子:
考虑贡献即为:
当我们将这个性质泛用到 \(i, j\) 时不难发现:
也就是说,只要 \(i, j\) 满足以上的两个性质就可以进行形如这样的计算。
于是考虑使用前缀数组分别对奇数位置和偶数位置的分别的贡献进行统计,这里考虑奇数位置统计的是 \(-2\times a_j - j\),偶数位置统计为 \(2 \times a_i - i\),这样子就可以得到求和的式子:
if (i & 1) {
ans = max(ans, tmp + (i-1));
ans = max(ans, tmp + pre2[i-1] - 2ll * a[i] + i);
} else {
ans = max(ans, tmp + (i-2));
ans = max(ans, tmp + 2ll * a[i] + i + pre1[i-1]);
}
这里同时考虑了同奇偶性质的位置的最大交换的贡献。
#include <bits/stdc++.h>
using namespace std;
#define ull unsigned long long
#define ll long long
const int N = 2e5+100;
const ll LNF = 1e18+100;
ll n, a[N], pre1[N], pre2[N];
void solve () {
cin >> n;
ll tmp = 0;
for (int i = 1;i <= n;i++) {
cin >> a[i];
if (i & 1) tmp += a[i];
else tmp -= a[i];
pre1[i] = pre2[i] = LNF*(-1);
}
pre1[0] = pre2[0] = LNF*(-1);
for (int i = 1;i <= n;i++) {
pre1[i] = pre1[i-1];
pre2[i] = pre2[i-1];
if (i & 1) pre1[i] = max(pre1[i], -2ll * a[i] - i);
else pre2[i] = max(pre2[i], 2ll * a[i] - i);
}
ll ans = LNF * (-1);
for (int i = 1;i <= n;i++) {
if (i & 1) {
ans = max(ans, tmp + (i-1));
ans = max(ans, tmp + pre2[i-1] - 2ll * a[i] + i);
} else {
ans = max(ans, tmp + (i-2));
ans = max(ans, tmp + 2ll * a[i] + i + pre1[i-1]);
}
}
cout << ans << "\n";
}
int main () {
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);
int _ = 1; cin >> _;
while (_--) solve();
return 0;
}
D
也是一道不错的题目,同样是贪心可解。
这里不难发现答案的初始值就是 \(\sum_{i = 1} ^ n (r_i-l_i)\)。
考虑到贪心思想,我们每次增加的区间的左右区间都应该是某两个区间分别的左端点和右端点,那么就可以将问题转换为每个区间决定自己应该贡献的是左端点还是右端点的问题。
我们首先考虑一个区间分别贡献左右端点的贡献,不难发现会是下面的两个式子:
- \(r-l-l\)
- \(r-l+r\)
发现下面的式子总会比上面的大,那么我们再次考量什么时候应该贡献右端点,什么时候该贡献左端点呢?
此时想到,如果左右端点隔得很近,那么我们就看这个区间具体是在很右边还是很左边,因为此时贡献左端点或者右端点的答案其实相差不大。
继而我们考虑如果隔得很远怎么办,发现其实和前面的情况很相像,我们要看的是综合的 r 和 l 的贡献程度,如果 r 和 l 隔得很近,在很右边就一定要选 r,在很左边就一定要选 l,那么根据贪心,我们在人脑计算时是一定会先处理这种 r 和 l 很近的区间去选 r 的,当我们处理了所有这种的区间的时候才会再去考虑 r 和 l 隔得很远但是 r 在很右边的情况,最后才去考虑 r 和 l 隔得很近但都在很左边的区间。
此时不难发现,区间的综合优先度可以用 \(r+l\) 来粗略估计,所以我们按照这个来对所有区间进行排序,然后对最靠前的一半的区间进行选右端点,其余左端点。
如果区间的个数为奇数的话,我们就需要考虑哪一个区间需要被我们抛弃,以及这个区间抛弃时是在选择左端点的一坨内还是选择右端点的一坨内即可。
#include <bits/stdc++.h>
using namespace std;
#define ull unsigned long long
#define ll long long
const int N = 2e5+100;
struct R {
int l, r, id;
}a[N];
int n;
bool cmp (R x, R y) {
if ((x.l+x.r) == (y.l+y.r)) return x.id < y.id;
return (x.l+x.r) > (y.l+y.r);
}
void solve () {
cin >> n;
for (int i = 1;i <= n;i++)
a[i].id = i, cin >> a[i].l >> a[i].r;
ll ans = 0;
for (int i = 1;i <= n;i++) ans += a[i].r - a[i].l;
sort(a+1, a+n+1, cmp);
if (!(n&1)) {
ll suml = 0, sumr = 0;
for (int i = 1;i <= n/2;i++) sumr += a[i].r;
for (int i = n/2+1;i <= n;i++) suml += a[i].l;
ans += (sumr-suml);
} else {
vector <ll> prer(n+1), prel(n+1);
ll res = 0;
for (int i = 1;i <= n;i++) {
prer[i] = prer[i-1] + a[i].r;
prel[i] = prel[i-1] + a[i].l;
}
for (int i = 1;i <= n/2+1;i++) res = max(res, prer[n/2+1]-a[i].r - (prel[n]-prel[n/2+1]));
for (int i = n/2+1;i <= n;i++) res = max(res, prer[n/2] - (prel[n]-prel[n/2]-a[i].l));
ans += res;
}
cout << ans << "\n";
}
int main () {
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);
int _ = 1; cin >> _;
while (_--) solve();
return 0;
}
浙公网安备 33010602011771号