AtCoder Beginner Contest 229
A. First Grid
解题思路
考虑两个对角线是不是存在一条都是 #
,另一条都是 .
,如果是则一定是 'No',否则就是 'Yes'。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
string s1, s2;
cin >> s1 >> s2;
if ((s1[0] == '#' && s2[1] == '#' && s1[1] == '.' && s2[0] == '.') || (
s1[0] == '.' && s2[1] == '.' && s1[1] == '#' && s2[0] == '#')) {
cout << "No\n";
} else {
cout << "Yes\n";
}
return 0;
}
B. Hard Calculation
解题思路
模拟加法过程,判断是否进位即可
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
string a, b;
cin >> a >> b;
reverse(a.begin(), a.end());
reverse(b.begin(), b.end());
int n = a.size(), m = b.size();
int t = 0;
bool ok = true;
for (int i = 0 ; i < min(n, m); i++) {
t += a[i] - '0' + b[i] - '0';
t /= 10;
if (t) ok = false;
}
if (ok) {
cout << "Easy\n";
} else {
cout << "Hard\n";
}
return 0;
}
C. Cheese
解题思路
考虑贪心,优先买 \(A_i\) 大的物品
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int n, w;
cin >> n >> w;
vector<array<int, 2>> c(n);
for (int i = 0; i < n; i++) {
cin >> c[i][0] >> c[i][1];
}
sort(c.begin(), c.end(), [&](array<int, 2> &x, array<int, 2> &y){
return x[0] > y[0];
});
LL ans = 0;
for (int i = 0; i < n; i++) {
int val = min(w, c[i][1]);
w -= val;
ans += 1LL * val * c[i][0];
}
cout << ans << '\n';
return 0;
}
D. Longest X
解题思路
经典的双指针或者二分题,题目相当于求满足 \(s_r-s_{l-1}\leq k\) 最长的区间,
其中 \(s_r - s_{l-1}\) 表示区间 \([l,r]\) 中 .
字符的数量。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
int cnt[N];
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
string s;
int k;
cin >> s >> k;
int n = s.size();
s = '?' + s;
for (int i = 1; i <= n; i++) {
cnt[i] = cnt[i - 1] + (s[i] == '.');
}
int ans = 0;
for (int i = 1, j = 1; i <= n; i++) {
while (j <= n && cnt[j] - cnt[i - 1] <= k) j++;
if (cnt[j - 1] - cnt[i - 1] <= k) {
ans = max(ans, j - i);
}
}
cout << ans << '\n';
return 0;
}
E. Graph Destruction
解题思路
由于删除操作不好考虑,考虑倒过来想,这是一个经典的 trick,我们倒过来看之后,每次
相当于加入一个点。我们用并查集维护目前的连通性即可,每次加入一个点,就看看这个点
能够连入多少条边。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
int p[N];
vector<int> g[N];
int ans[N];
bool st[N];
int find(int x) {
return x == p[x] ? x : p[x] = find(p[x]);
}
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int n, m;
cin >> n >> m;
while (m--) {
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
for (int i = 1; i <= n; i++) p[i] = i;
int res = 0;
for (int i = n; i >= 1; i--) {
ans[i] = res;
st[i] = true;
//恢复 i 这个点
res++;
for (auto j : g[i]) {
if (st[j]) {
int u = find(i), v = find(j);
if (u != v) {
p[u] = v;
res--;
}
}
}
}
for (int i = 1; i <= n; i++) {
cout << ans[i] << '\n';
}
return 0;
}
F. Make Bipartite
解题思路
首先是一个二分图 \(\iff\) 图里面所有点可以被黑白染色,且满足有边相连的点颜色必须不一样。我们不失一般性,可以设 \(0\) 号点染白色,然后问题就变成了一个环上染色问题,考虑经典的 DP。
我们不妨设 \(dp_{i,0/1,0/1}\) 表示考虑了前 \(i\) 个点,且这个点染 \(0/1\) 白色还是黑色,且
\(1\) 号点染了 \(0/1\) 白色还是黑色。
转移方程如下:
最后枚举答案的时候,记得讨论以下第 \(n\) 个点的颜色是否和第一个一样,一样则要加上 \(b[n]\)。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
int a[N], b[N];
LL dp[N][2][2];
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 1; i <= n; i++) {
cin >> b[i];
}
memset(dp, 0x3f, sizeof dp);
dp[1][0][0] = a[1]; //白色
dp[1][1][1] = 0; // 黑
for (int i = 2; i <= n; i++) {
for (int j = 0; j < 2; j++) {
for (int k = 0; k < 2; k++) {
for (int p = 0; p < 2; p++) {
dp[i][j][k] = min(dp[i][j][k], dp[i - 1][p][k] + (p == j ? b[i - 1] : 0) + (j == 0 ? a[i] : 0));
}
}
}
}
LL ans = 1e18;
for (int i = 0; i < 2; i++) {
for (int k = 0; k < 2; k++) {
ans = min(ans, dp[n][i][k] + (i == k ? b[n] : 0));
}
}
cout << ans << '\n';
return 0;
}
G. Longest Y
解题思路
- 首先对于 \(..Y...Y...Y..Y..\),最优解肯定是固定中间一个 \(Y\) 不动,其它 \(Y\) 往中间移动,这样的次数肯定是最少的。
- 对于两个 \(Y\),设其是序列中的第 \(i\) 个 \(Y\) 和第 \(j\) 个 \(Y\),位置分别是 \(p_i,p_j\)
那么把他们两个移动到一起的代价是:\(|(p_i-i)-(p_j-j)|\),因为考虑到 \(j\) 前面还有一些 \(Y\)是需要移动到 \(i\) 旁边的,所以我们还需要算上 \(|i-j|\) 即这两个 \(Y\) 的相对距离。
那么我们令序列 \(a_i=p_i-i\),\(p_i\) 表示某个 \(Y\) 在原序列的位置。
问题变成了找到连续一段最长的区间,使得 \([l,r]\) 的代价 \(\leq k\)。
根据经典的中位数贪心模型,显然区间 \([l,r]\) 的代价为:\(\sum_{i=l}^r|a_i-a_{mid}|\)。
然后考虑到答案具有二分性,考虑二分这个答案,然后 \(check\) 以下这个序列是否存在一个长度为 \(x\) 的区间满足其代价 \(\leq k\)。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
int a[N];
LL pre[N];
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
string s;
LL k;
cin >> s >> k;
int n = 0;
for (int i = 0; i < s.size(); i++) {
if (s[i] == 'Y') {
a[++n] = i + 1 - n;
}
}
for (int i = 1; i <= n; i++) {
pre[i] = pre[i - 1] + a[i];
}
auto check = [&](int x) {
for (int i = 1; i + x - 1 <= n; i++) {
int l = i, r = i + x - 1;
int mid = (l + r) >> 1;
if (1LL * (mid - l + 1) * a[mid] - (pre[mid] - pre[l - 1]) + pre[r] - pre[mid] - 1LL * (r - mid) * a[mid] <= k) return true;
}
return false;
};
int l = 0, r = n;
while (l < r) {
int mid = (l + r + 1) >> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
cout << l << '\n';
return 0;
}