Codeforces Round 1040 (Div. 2) A~D题解
A.Submission is All You Need
一、题意简述
给你一个长为 \(n\) 的序列 \(a\),你可以把序列中若干元素求和,或取 mex,求累加答案最大值。
二、思路
我们可以先分析 mex 操作。
我们很容易证明除了 \(0,0,...,0\) 和 \(0,1,2,...,n\) 以外的所有序列取 mex 一定会变小,而 \(0,1,2,...,n\) 取 mex 得到的答案为 \(n+1\),显然没有直接求和更优,所以除了 \(0\) 取 mex,其他都要求和。
而 \(0,0,0,...,0\) 分开取 mex 肯定是要比一起去更优,如 0 0 0 0 取 mex 为 1,而分成 4 个 0 取 mex 为 4。
三、结论&代码
所以答案很明显,把所有 0 改为 1(mex 操作),然后统一求和。
关键代码:
inline void solve()
{
int n;cin >> n;
ll ans = 0;
rep(i,1,n,1) {
int x;cin >> x;
ans += max(1,x);
}
cout << ans << endl;
}
B.Pathless
一、题意简述
给你一个由 \(0,1,2\) 组成的序列 \(a\),你最开始的位置 \(x=1\),你可以通过 \(x-1\) 或 \(x+1\) 走到 \(n\),同时你会得到 \(a_x\) 个积分,问是否有办法让我无论怎样走都不能恰好得到 \(s\) 个积分,输出方案,无解输出 -1。
二、思路
首先,对于 \(\sum a_i > s\) 的情况,怎样排都可以。
然后,对于 \(\sum a_i = s\) 的情况,输出 -1。
派出了以上两种情况,我们就可以讨论 \(\sum a_i < s\) 的情况了。
注意到一定会出现 0 1 和 1 2 至少其中之一,那么 \(sum - s = 3k\) 就能被构造出来了,可以通过 \(k\) 次 1 2 和 \(3k\) 次 0 1 来解决。
同理,会出现 0 1 和 0 2 至少其中之一,那么 \(sum - s = 2k\) 就能被构造出来了。
根据斐蜀定理的推论,我们能同时构造出 2 的倍数和 3 的倍数,就一定能构造出所有 \(> 2 \times 3 - 2 - 3\) 的数,及构造出所有大于 1 的数。
三、结论&代码
所以只有当 \(s - sum = 1\) 时,可以构造,这是我采用的构造方法:000...0111...1222...2。
核心代码:
void solve() {
int n, s;
cin >> n >> s;
vector<int> a(n + 1, 0);
int sum = 0;
int zero = 0;
int one = 0;
for (int i = 1; i <= n; i++) {
cin >> a[i];
sum += a[i];
if (a[i] == 0) zero++;
if (a[i] == 1) one++;
}
if (sum == s) {
cout << -1 << endl;
return;
}
if (sum > s || sum == s - 1) {
for (int i = 1; i <= zero; i++) cout << "0 ";
int x = n - zero - one;
while (x--) cout << "2 ";
for (int i = 1; i <= one; i++) cout << "1 ";
cout << endl;
return;
}
cout << "-1" << endl;
}
C.Double Perspective
一、题意简述
给你 \(n\) 个数对 \([a_i,b_i]\),让你从中选若干个,使得区间覆盖的面积减去环的个数最大。
二、思路
我们假设现在由一条 \([a_i,b_i]\) 的边,那么我们要让覆盖的面积最大,就看这一段是否被覆盖过了。
-
被覆盖过:肯定不会增加覆盖的面积,但是由于 \([a_i,b_i]\) 中多了一条边,对于环的个数不会减小,还可能会增加,所以此时的最优策略是不连。
-
否则, \(b_i\) 一定是一个新节点或 \(a_i,b_i\) 并不在一个连通块内,此时环的个数不会增加,因为连通块之间只有一条边连着,所以此时的最优策略是连。
三、结论&代码
代码就很简单了,注意并查集别写错了
核心代码:
void init() {
for (int i = 0; i < N; i++) fa[i] = i;
}
int find(int x) {
if (x == fa[x]) return x;
return fa[x] = find(fa[x]);
}
bool is(int x, int y) {
x = find(x);
y = find(y);
return (x == y ? true : false);
}
void join(int x, int y) {
x = find(x);
y = find(y);
if (x == y) return ;
fa[x] = y;
}
void solve() {
init();
vector<int> ans;
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
int u, v;
cin >> u >> v;
if (!is(u, v)) {
ans.push_back(i);
join(u, v);
}
}
cout << ans.size() << endl;
for (auto &i : ans) cout << i << ' ';
cout << endl;
}
D.Stay or Mirror
一、题意简述
给你一个长为 \(n\) 的排列 \(p\),你需要构造一个序列 \(a\),\(a_i=p_i\) 或 \(a_i=2n-p_i\),使得序列 \(a\) 的逆序对尽可能少。
二、思路
首先我们要明确:\(p \le n\),那么 \(2p \le 2n,p\le2n - p\),所以我们选第二个相当于在放大 \(p_i\)。
为了方便,我们设 \(l_i\) 为第 \(1\sim i-1\) 元素比 \(p_i\) 大的数量,\(r_i\) 为 \(i+1\sim n\) 比 \(p_i\) 小的数量。
我们考虑当第 \(i\) 个位置选 x 时的贡献:
- 前面,所有大于 \(x\) 的,贡献为 \(l_i\)。
由于 \(2n - x\) 已经大于 \(n\) 了,所以 \(l\) 的贡献为 \(0\),所以变换后的贡献是 x 后的:
- 后面,所有大于 \(2n-x\) 的,贡献为 \(r_i\)。
证明:原来小于 \(x\) 的数会在遍历到他的时候再计算,而原来小于 \(x\) 的数在 \(x\) 变为 \(2n-x\) 的时候都会小于 \(2n-x\),所以这时候的贡献就为原来右边大于 \(2n-x\) 的元素的个数。
三、结论&代码
对于第 \(i\) 个数,选择 \(\min (l_i,r_i)\) 进行累加。
核心代码:
void slove() {
ll n;
cin >> n;
for (ll i = 1; i <= n; i++) {
cin >> s[i];
}
ll ans = 0;
for (ll i = 1; i <= n; i++) {
ll l = 0, r = 0;
for (ll j = 1; j < i; j++)
if (s[j] > s[i])l++;
for (ll j = i + 1; j <= n; j++)
if (s[j] > s[i])r++;
ans += min(l, r);
}
cout << ans << endl;
}

浙公网安备 33010602011771号