比赛链接:
https://codeforces.com/contest/1660
D. Maximum Product Strikes Back
题目大意:
一个整数序列,元素范围从 -2 到 2,可以删除它头部的一些元素或者尾部的一些元素,判断怎么删除才能使剩下的元素的积最大。输出删除多少个头部的元素以及多少个尾部的元素。(整个序列都删除之后积为 1)
思路:
首先按照 0 的位置将整个序列分成几段,因为乘上 0 之后答案为 0,还不如整个序列都删掉。
接着每一段求一个和,若为正数,则直接和之前计算的最大值比较一下,若为负数,则要删除这一段中一个负数,根据贪心的策略,肯定删除该段左往右第一个负数及它左边的所有数,或者右往左第一个负数及它右边的所有数,选择一个最大的。
比较大小的时候只需要考虑 2 的数量即可,直接乘的话最大值为 \(2^200000\),会炸掉。
代码:
#include <bits/stdc++.h>
using namespace std;
int T = 1, n;
void solve(){
cin >> n;
vector <int> a(n + 1);
for (int i = 1; i <= n; i ++ ) cin >> a[i];
int p = 1, l = n, r = 0, ans = 0;
for (int i = 1; i <= n; i ++ ){
if (i == n || a[i] == 0){
int cnt = 0, s = 1, t = i - (a[i] == 0);
for (int j = p; j <= t; j ++ ){
if (a[j] < 0) s *= -1;
if (abs(a[j]) == 2) cnt++;
}
if (s > 0){
if (cnt > ans){
ans = cnt;
l = p - 1;
r = n - t;
}
}
else {
int dl = cnt, dr = cnt, ll = p, rr = t;
for (; a[ll] > 0; ll ++ )
if (abs(a[ll]) == 2)
dl--;
if (abs(a[ll]) == 2) dl--;
ll++;
for (; a[rr] > 0; rr -- )
if (abs(a[rr]) == 2)
dr--;
if (abs(a[rr]) == 2) dr--;
rr--;
if (dl > dr){
if (dl > ans){
ans = dl;
l = ll - 1;
r = n - t;
}
}
else{
if (dr > ans){
ans = dr;
l = p - 1;
r = n - rr;
}
}
}
p = i + 1;
}
}
cout << l << " " << r << "\n";
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
cin >> T;
while (T--)
solve();
return 0;
}
E. Matrix and Shifts
题目大意:
一个 \(n * n\) 的每个元素为 0 或 1 的矩阵。
有两种操作:
一、所有行或者列周期移动,即所有行往下移动,第 \(n\) 行移动到第 1 行,或者所有行上移,第 1 行移动到第 \(n\) 行,列的移动也相同,这步操作免费。
二、选择一个格子,让它的值和 1 取异或,花费 1。
问最少花费多少使得矩阵变成单位矩阵。
思路:
第二步操作就是让 0 和 1 互相转化。为了变成单位矩阵,所有正对角线上的数要变成 1,其它值变为 0。
可以发现第一步操作不改变斜线上的 1 的数量。为了是第二步的花费最小,应该让 1 最多的那条斜线移动到正对角线。
所以直接计算每一条斜线上 1 的数量,让最大的那个作为正对角线,然后将斜线上的 0 变成 1,其它地方的 1 变成 0。
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 2e3 + 10;
int T = 1, a[N][N], n;
int cal(int x, int y){
int cnt = 0;
while (y <= n){
cnt += a[x][y];
x = x % n + 1;
y++;
}
return cnt;
}
void solve(){
cin >> n;
int s = 0, mx = 0;
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ ){
char c;
cin >> c;
if (c == '1'){
a[i][j] = 1;
s++;
}
else a[i][j] = 0;
}
for (int i = 1; i <= n; i ++ )
mx = max(mx, cal(i, 1));
cout << n - mx + s - mx << "\n";
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
cin >> T;
while (T--)
solve();
return 0;
}
F1. Promising String (easy version)
题目大意:
一个只包含 '+' 和 '-' 的字符串,当一个子串中的 '+' 和 '-' 数量相等的话,它被称为平衡子串。若让子串中连续的两个 '-' 变成 '+' 后,子串平衡,这个子串也是平衡子串。计算该字符串中平衡子串的数量。
思路:
easy version,数据量小,先预处理一下 '+' 和 '-' 的数量的前缀和,然后枚举每一个区间就可以了。
当 '+' 和 '-' 的数量相等或者 '-' 的数量比 '+' 的数量多 3 的倍数个,都是平衡的,因为可以改变两个连续 '-' 变为 '+'。
代码:
#include <bits/stdc++.h>
using namespace std;
int T = 1, n;
string s;
void solve(){
cin >> n >> s;
vector <int> a(n + 1);
for (int i = 0; i < n; i ++ )
if (s[i] == '+') a[i + 1] = a[i] + 1;
else a[i + 1] = a[i] - 1;
int ans = 0;
for (int i = 1; i <= n; i ++ )
for (int j = i + 1; j <= n; j ++ )
if (a[j] == a[i - 1] || ( (a[j] - a[i - 1]) % 3 == 0 && a[j] <= a[i - 1]) )
ans++;
cout << ans << "\n";
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
cin >> T;
while (T--)
solve();
return 0;
}
F2 - Promising String (hard version)
思路:
暴力的基础上进行优化,若某段区间的 '-' 的数量减去 '+' 的数量的结果是 3 的倍数,那么这一段区间一定是可以的。
计算出 '-' 的数量的前缀和,那么要求的就是在 \(i\) 之前的所有前缀和小于它且差值为 3 的倍数的数,即找到 \(j\),满足 \(a[j] <= a[i]\),\(j < i\),\((a[j] - a[i])\) % 3 == 0 。
用树状数组来维护这个值的计算。
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
#define LL long long
string s;
LL T = 1, n, tree[3][2 * N + 1], a[N];
LL lowbit(LL k){
return k & -k;
}
void update(LL x, LL k, LL p){
while (x <= 2 * n + 1){
tree[p][x] += k;
x += lowbit(x);
}
}
LL query(LL x, LL p){
LL t = 0;
while( x > 0 ){
t += tree[p][x];
x -= lowbit(x);
}
return t;
}
void solve(){
cin >> n >> s;
for (int i = 0; i <= 2 * n + 1; i ++ )
tree[0][i] = tree[1][i] = tree[2][i] = 0;
for (int i = 0; i < n; i ++ )
a[i + 1] = a[i] + (s[i] == '-' ? 1 : -1);
LL ans = 0;
update(n + 1, 1, 0);
for (int i = 1; i <= n; i ++ ){
LL k = (a[i] % 3 + 3) % 3;
ans += query(n + a[i] + 1, k);
update(n + a[i] + 1, 1, k);
}
cout << ans << "\n";
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
cin >> T;
while (T--)
solve();
return 0;
}