AtCoder Regular Contest 119 VP 记录
赛时半小时过 ABC,最后和核堆分析出 E 来了,不过我赛时没写完。算是做的比较顺的一场 ARC。
A - 119 × 2^23 + 1
给你一个数 \(n\),找到最小的 \(a+b+c\) 满足 \(a \times 2^b + c = n\)。
显然,枚举一个 \(b\),然后 \(a\) 越大越优,所以 \(a = \left \lfloor \frac{n}{2^b} \right \rfloor, c = n - \left \lfloor \frac{n}{2^b} \right \rfloor \times 2^b\)。
对所有情况取个 \(\min\) 即可。
signed main() {
n = read();
ans = n;
for(int i = 1, p = 0; i <= n; i <<= 1, ++p) {
ans = min(ans, n / i + n % i + p);
}
cout << ans << "\n";
return 0;
}
B - Electric Board
给你两个长度为 \(n\) 的字符串 \(s,t\),问能否由 \(s\) 变为 \(t\)。
变换规则是:选择一个 \(0\) 和相邻的若干个 \(1\) 交换位置。例如 \(0111 \to 1110\)。
有无解是两个字符串 \(0,1\) 个数不同。
答案是两个字符串有多少位置不同。
为什么呢,每次贪心的移动,都至少会让一个 \(0\) 相对应。
signed main() {
n = read();
cin >> s + 1 >> t + 1;
for(int i = 1; i <= n; ++i) if(s[i] == '0') a[++topa] = i;
for(int i = 1; i <= n; ++i) if(t[i] == '0') b[++topb] = i;
if(topa != topb) return puts("-1"), 0;
int ans = 0;
for(int i = 1; i <= topa; ++i) {
if(a[i] != b[i]) ans ++;
}
cout << ans << "\n";
return 0;
}
C - ARC Wrecker 2
给你一个长度为 \(n\) 的序列。有两种操作:
- 一对相邻的位置同时 \(+1\);
- 一对相邻的位置同时 \(-1\);
问有多少区间 \([l,r]\) 经过一定变化后可以变成 \(0\)。
发现如果把奇数位和偶数为分开,那么每次操作,两个位数的和只差不会发生变化。
所以只需要统计一下奇偶位上的和是否相同就可以了
奇数位为正,偶数位为负。
前缀和,套上 map,随便做。
signed main() {
n = read();
for(int i = 1; i <= n; ++i) a[i] = read();
for(int i = 1; i <= n; ++i) if(i & 1) a[i] = - a[i];
Map[0] ++;
for(int i = 1; i <= n; ++i) {
a[i] = a[i - 1] + a[i];
ans += Map[a[i]];
Map[a[i]] ++;
}
cout << ans << "\n";
return 0;
}
D - Grid Repainting 3
给你一个 \(n \times m\) 的表格。每个位置有红和蓝两种颜色,每次可以选择一个红色颜色的位置,将其所在位置的一行或者一列变成白色。问最后最多能将多少位置变成白色。
\(1 \le n, m \le 2500\)。
让我们把每行每列看作一个点,然后如果 \((i,j)\) 这个位置是红色,那么第 \(i\) 行的点和第 \(j\) 列的点连边。
那么对于一个连通块,每次操作就是选择一个点,删掉与它相连的所有边。
显然对于任意一个连通块,最后一定能被删的只剩下一个点,因为可以构造出它的任意一个生成树,然后从度为 \(1\) 的点不断删,最后只剩一个点,而且剩的这个点是行点还是列点还都可以。
进一步分析,对于所有连通块,发现我们最后剩下的点,一定都是一种颜色。
所以,用 dfs
确定一下删的顺序就好啦。
#include<bits/stdc++.h>
#define LL long long
//#define int long long
#define orz cout << "tyy YYDS!!!\n"
using namespace std;
const int MAXN = 2555;
const int INF = 1e9 + 7;
const int mod = 998244353;
int read() {
int s = 0, f = 0; char ch = getchar();
while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
while(isdigit(ch)) s = (s << 1) + (s << 3) + (ch ^ 48), ch = getchar();
return f ? -s : s;
}
struct node {
int opt, x, y;
}ans[MAXN * MAXN];
int n, m, top = 0;
char s[MAXN][MAXN];
vector<int> E[MAXN << 1];
bool vis[MAXN << 1];
void dfs(int u) {
vis[u] = true;
for(auto v : E[u]) {
if(vis[v]) continue;
dfs(v);
if(u <= n) ans[++top] = (node){0, u, v - n};
else ans[++top] = (node){1, v, u - n};
}
}
signed main() {
n = read(), m = read();
for(int i = 1; i <= n; ++i) cin >> s[i] + 1;
int cnt0 = n, cnt1 = m;
for(int i = 1; i <= n; ++i) {
for(int j = 1; j <= m; ++j) {
if(s[i][j] != 'R') continue;
if(!E[i].size()) cnt0--;
if(!E[n + j].size()) cnt1--;
E[i].push_back(n + j), E[n + j].push_back(i);
}
}
if(cnt0 > cnt1) {
for(int i = 1; i <= n; ++i) if(!vis[i]) dfs(i);
} else {
for(int i = n + 1; i <= n + m; ++i) if(!vis[i]) dfs(i);
}
printf("%d\n", top);
for(int i = 1; i <= top; ++i) {
printf("%c %d %d\n", ans[i].opt ? 'X' : 'Y', ans[i].x, ans[i].y);
}
return 0;
}
E - Pancakes
给你一个长度为 \(n\) 的序列,反转一段区间,使得 \(\sum_{i=2}^{n} |a_i - a_{i-1}|\) 最小。
\(n \le 3 \times 10^5\)。
发现操作最多只会改变两个位置的差值。
考虑什么时候答案会更优?设交换的区间是 \([l,r]\)。
假设 \(a_{l-1} < a_{l}, a_r < a_{r+1}\)
发现只有 \([a_{l-1},a_l]\) 和 \([a_r, a_{r+1}]\) 有交集的时候,会让答案减少 \(2|S|\),其中 \(|S|\) 指的是交集的大小。
然后就简单了,按照 \(a_{i-1} < a_{i}\) 和 \(a_{i-1} > a_i\) 分类。
对每一类求一下里面最大的两个集合的交即可。
对于交换的区间是 \([1,r], [l,n]\) 的情形可以拿出来单独处理。
对于线段集合的最大交,这是一个比较经典的问题。
可以对里面的线段按照左端点排序,先处理出一条线段被另一条线段包含的情况。
然后就是在剩下的线段中取相邻两个线段的交的最大值。
#include<bits/stdc++.h>
#define LL long long
#define int long long
#define orz cout << "tyy YYDS!!!\n"
using namespace std;
const int MAXN = 4e5 + 10;
const int INF = 1e9 + 7;
const int mod = 998244353;
struct node { int l, r; };
int n, ans = 0;
int a[MAXN];
vector<node> b, c;
int stc[MAXN];
int read() {
int s = 0, f = 0; char ch = getchar();
while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
while(isdigit(ch)) s = (s << 1) + (s << 3) + (ch ^ 48), ch = getchar();
return f ? -s : s;
}
bool cmp(node x, node y) { return x.l < y.l; }
int Solve(vector<node> a) {
sort(a.begin(), a.end(), cmp);
int res = 0, sc = 0, M = a.size();
for(int i = 0; i < M; ++i) {
if(sc && a[stc[sc]].r >= a[i].r) res = max(res, a[i].r - a[i].l);
else stc[++sc] = i;
}
for(int i = 0; i < sc; ++i) a[i] = a[stc[i + 1]];
for(int i = 1; i < sc; ++i)
if(a[i].l <= a[i - 1].r)
res = max(res, a[i - 1].r - a[i].l);
return res;
}
signed main() {
n = read();
for(int i = 1; i <= n; ++i) a[i] = read();
for(int i = 2; i <= n; ++i) ans += abs(a[i] - a[i - 1]);
for(int i = 2; i <= n; ++i) {
if(a[i - 1] <= a[i]) b.push_back((node){a[i - 1], a[i]});
if(a[i - 1] >= a[i]) c.push_back((node){a[i], a[i - 1]});
}
int del = 0;
for(int i = 2; i <= n; ++i) {
del = min(del, abs(a[i] - a[1]) - abs(a[i] - a[i - 1]));
del = min(del, abs(a[n] - a[i - 1]) - abs(a[i] - a[i - 1]));
}
int res1 = Solve(b), res2 = Solve(c);
ans = min(ans - 2 * max(res1, res2), ans + del);
printf("%lld\n", ans);
return 0;
}