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\) 白色还是黑色。
转移方程如下:

\[dp[i][j][k]=\min(dp[i][j][k], dp[i-1][p][k] + (j==0? a[i]:0) + (j == p ? b[i-1]:0)) \]

最后枚举答案的时候,记得讨论以下第 \(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; 
}
posted @ 2023-09-20 22:22  jackle  阅读(6)  评论(0编辑  收藏  举报