刷Atcoder 二周目

AT_arc166_a [ARC166A] Replace C or Swap AB

题目描述

给定由 ABC 组成的长度为 \(N\) 的字符串 \(X\)\(Y\)

你可以对 \(X\) 进行以下三种操作(每种操作可以执行任意次,包括 \(0\) 次),请判断是否可以将 \(X\) 变为 \(Y\)

  • 操作 (1):选择 \(X\) 中的一个 C 字符,将其替换为 A
  • 操作 (2):选择 \(X\) 中的一个 C 字符,将其替换为 B
  • 操作 (3):选择 \(X\) 中的一个子串 AB,将其替换为 BA。更形式化地说,选择 \(X\) 中第 \(i\) 个字符为 A 且第 \(i+1\) 个字符为 B\(i\),将第 \(i\) 个字符替换为 B,第 \(i+1\) 个字符替换为 A

给定 \(T\) 组测试数据,请分别回答每组数据是否可以将 \(X\) 变为 \(Y\)

题解

A 串中没有任何方法可以在一个不是 C 的位置上创造一个 C,即在 B 串中是 C 的位置在 A 串中初始也必须是 C 而且这个位置的 C 不能被改变,如果有一个位置 B 串中是 C 但是 A 串中不是,那么直接输出 No 即可。否则,A 和 B 两个串均会被起点、终点和每一个 C 分成若干块。

不难发现,操作二和操作三只能在每个块内操作,而且对于 A 串来说,其中的 A 只能向后移动,B 只能向前移动,然后直接在块内模拟处理即可。

Code

#include <bits/stdc++.h>
using namespace std;
bool check(string s, string t) {
	int n = s.size();
	int ca = 0, cb = 0, cc = 0;
	int ta = 0, tb = 0;
	for (char c : s) {
		if (c == 'A') ca++;
		else if (c == 'B') cb++;
		else cc++;
	}
	for (char c : t) {
		if (c == 'A') ta++;
		else tb++;
	}
	int nd1 = ta - ca;
	int nd2 = tb - cb;
	if (nd1 < 0 || nd2 < 0 || nd1 + nd2 != cc) return false;
	int cur = 0;
	for (int i = 0; i < n; i++) {
		if (s[i] == 'A' || s[i] == 'C') cur++;
		if (t[i] == 'A') {
			if (cur == 0) return false;
			cur--;
		}
	}
	cur = 0;
	for (int i = n - 1; i >= 0; i--) {
		if (s[i] == 'B' || s[i] == 'C') cur++;
		if (t[i] == 'B') {
			if (cur == 0) return false;
			cur--;
		}
	}
	return true;
}
void solve() {
	int n;
	string x, y;
	cin >> n >> x >> y;
	bool ok = true;
	for (int i = 0; i < n; i++) {
		if (y[i] == 'C' && x[i] != 'C') {
			ok = false;
			break;
		}
	}
	if (!ok) {
		cout << "No\n";
		return;
	}
	int i = 0;
	while (i < n) {
		if (y[i] == 'C') {
			i++;
			continue;
		}
		int j = i;
		while (j < n && y[j] != 'C') j++;
		string s = x.substr(i, j - i);
		string t = y.substr(i, j - i);
		if (!check(s, t)) {
			ok = false;
			break;
		}
		i = j;
	}
	cout << (ok ? "Yes" : "No") << '\n';
}
int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	int T;
	cin >> T;
	while (T--) {
		solve();		
	}
	return 0;
}

AT_abc282_d [ABC282D] Make Bipartite 2

题目描述

给定一个包含 \(N\) 个顶点和 \(M\) 条边的简单无向图 \(G\)(即不包含自环和重边)。对于 \(i = 1, 2, \ldots, M\),第 \(i\) 条边连接顶点 \(u_i\) 和顶点 \(v_i\)

请输出满足下列两个条件的整数对 \((u, v)\) 的个数,其中 \(1 \leq u < v \leq N\)

  • 在图 \(G\) 中,顶点 \(u\) 和顶点 \(v\) 之间不存在边。
  • 在图 \(G\) 中添加一条连接顶点 \(u\) 和顶点 \(v\) 的边后,所得的图仍然是二分图。

什么是二分图?无向图被称为二分图,当且仅当可以将所有顶点染成黑色或白色,使得不存在连接同色顶点的边。

题解

首先如果整个图有一个连通块不是二分图就 GG 了,显然怎么连都不可能把它变成二分图。

然后考虑两种情况:

  • 连通块内连接。我们对于每一个连通块 dfs 染色,在判断是否是二分图同时记录黑白点数量,显然我们只能连接白到黑的边,方案数为白黑点数量相乘。
  • 连通块之间连接。假设这个连通块大小是 \(siz\),那么它可以和其他连通块连接,结果也肯定是二分图,贡献为 \(\frac{siz \times (n - siz)}{2}\)

不要忘了最后要减去原来就有的 \(m\) 条边。

Code

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int M = 2e5 + 5;
vector<int>vec[M];
int col[M];
ll w, b;
void dfs(int u, int fa) {
	if(col[u] == 1) b++;
	else w++;
	for(auto v : vec[u]){
		if(v == fa) continue;
		if(col[v] == -1) col[v] = col[u] ^ 1, dfs(v, u);
		else if(col[v] == col[u]){
			printf("0");
			exit(0);
		}
	}
}
int main() {
	int n, m;
	ll ans = 0, sum = 0;
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; i++) {
		int u, v;
		scanf("%d%d", &u, &v);
		vec[u].push_back(v);
		vec[v].push_back(u);
	}
	for (int i = 1; i <= n; i++) col[i] = -1;
	for (int i = 1; i <= n; i++) {
		if (col[i] == -1) {
			col[i] = 1;
			w = b = 0;
			dfs(i, 0);
			ans += w * b;
			sum += (w + b) * (n - w - b);
		}
	}
	printf("%lld", ans + sum / 2 - m);
	return 0;
}

AT_arc175_a [ARC175A] Spoon Taking Problem

题目描述

\(N\) 个人围坐在圆桌旁,每个人按逆时针方向依次编号为 \(1,\ 2,\ \ldots,\ N\)。每个人都有一只惯用手,可能是左手或右手。

圆桌上有 \(N\) 把编号为 \(1,\ 2,\ \ldots,\ N\) 的勺子,每两个人之间放有一把勺子。对于每个 \(1 \leq i \leq N\),第 \(i\) 个人的左侧有勺子 \(i\),右侧有勺子 \((i+1)\)。这里,勺子 \((N+1)\) 指的是勺子 \(1\)

给定一个 \((1,\ \dots,\ N)\) 的排列 \((P_1,\ \dots,\ P_N)\)。按照 \(i=1,\dots,N\) 的顺序,第 \(P_i\) 个人依次进行如下操作:

  • 如果自己左侧或右侧还有勺子,则从中取走一把。
    • 如果自己两侧的勺子都还在,则取走与自己惯用手相同侧的勺子。
  • 否则什么也不做。

给定一个由 LR? 组成的长度为 \(N\) 的字符串 \(S\)\(N\) 个人的惯用手组合共有 \(2^N\) 种可能,请你计算其中满足以下所有条件的组合数,并对 \(998244353\) 取模:

  • 如果 \(S\) 的第 \(i\) 个字符为 L,则第 \(i\) 个人必须是左撇子。
  • 如果 \(S\) 的第 \(i\) 个字符为 R,则第 \(i\) 个人必须是右撇子。
  • 所有人操作结束后,每个人都拿到了一把勺子。

题解

我们注意到,拿完的情况肯定是每个人只拿右手边或左手边的勺子才可能。

所以我们强制让第一个拿的人拿左/右,枚举剩下人的选择,如果只有一边有勺子,那么 TA 是左撇子或右撇子无所谓,答案 \(\times 2\);如果没勺子可以取答案清零。将两种答案加起来就是最终的答案。

Code

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int mod = 998244353;
const int M = 2e5 + 5;
int p[M], vis[M];
int main() {
	int n;
	cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> p[i];
        p[i]--;
	}
	string s;
	cin >> s;
	ll ans1 = 1, ans2 = 1;
	for (int i = 1; i <= n; i++) {
		int rt = (p[i] + 1) % n;
		if (vis[rt]) {
			if (s[p[i]] == '?') ans1 = (ans1 * 2) % mod;
		} else {
			if (s[p[i]] == 'R') ans1 = 0;
		}
		int lf = (p[i] - 1 + n) % n;
		if (vis[lf]) {
			if (s[p[i]] == '?') ans2 = (ans2 * 2) % mod;
		} else {
			if (s[p[i]] == 'L') ans2 = 0;
		}
		vis[p[i]] = true;
	}
	cout << (ans1 + ans2) % mod;
	return 0;
}

AT_arc166_b [ARC166B] Make Multiples

题目描述

给定一个整数序列 \(A=(A_1,\ldots,A_N)\),以及正整数 \(a, b, c\)

你可以对该数列进行如下操作(可以进行任意次,包括 \(0\) 次):

  • 选择一个整数 \(i\),其中 \(1\leq i\leq N\),将 \(A_i\) 替换为 \(A_i+1\)

你的目标是使数列 \(A\) 中至少各有一个元素是 \(a\) 的倍数、\(b\) 的倍数、\(c\) 的倍数。请你求出达成目标所需的最小操作次数。

题解

一个显然的状压 dp。

我们记 \(dp_{i,j}\) 满足前 \(i\) 个数中满足限制 \(j\) 的最小操作次数,那么我们有 \(dp_{i,S} = dp_{i-1, T} + to(a_i, lcm(T))\) ,其中 \(T \in S, to(a_i, lcm(T))\) 表示把 \(a_i\) 变成 \(lcm(T)\) 的操作数。

Code

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int M = 2e5 + 5;
const ll inf = LLONG_MAX / 2;
ll arr[M], dp[M][8], lm[8];
int b[4];
ll lcm(int x, int y){
	return x / __gcd(x, y) * y;
}
ll get(ll x, ll y) {
	ll pos = (x + y - 1) / y;
	return pos * y - x;
}
int main() {
	int n;
	scanf("%d", &n);
	for (int i = 0; i < 3; i++) {
		scanf("%d", &b[i]);
	}
	lm[0] = 1;
	for (int i = 1; i < 8; i++) {
		for (int j = 0; j < 3; j++) {
			if ((i >> j) & 1) {
				lm[i] = lcm(b[j], lm[i ^ (1 << j)]);
			}
		}
	}
	dp[0][0] = 0;
	for (int i = 1; i <= 7; i++) dp[0][i] = inf;
	for (int i = 1; i <= n; i++){
		ll x;
		scanf("%lld", &x);
		for(int j = 0; j <= 7; j++){
			dp[i][j] = dp[i - 1][j];
			for(int k = 1; k <= 7; k++){
				if((j & k) == k){ // k in j
					dp[i][j] = min(dp[i][j], dp[i - 1][j ^ k] + get(x, lm[k]));
				}
			}
		}
	}
	printf("%lld", dp[n][7]);
	return 0;
}

AT_abc244_e [ABC244E] King Bombee

题目描述

给定一个有 \(N\) 个顶点 \(M\) 条边的简单无向图。图中的顶点编号为 \(1\)\(N\),边编号为 \(1\)\(M\)。第 \(i\) 条边连接顶点 \(U_i\) 和顶点 \(V_i\)

给定整数 \(K,\ S,\ T,\ X\)。请问满足以下条件的数列 \(A = (A_0, A_1, \dots, A_K)\) 有多少种?

  • \(A_i\)\(1\)\(N\) 之间的整数。
  • \(A_0 = S\)
  • \(A_K = T\)
  • 对于所有 \(0 \leq i < K\),顶点 \(A_i\) 和顶点 \(A_{i+1}\) 之间存在直接相连的边。
  • 在数列 \(A\) 中,整数 \(X\)(且 \(X \neq S, X \neq T\))出现的次数为偶数次(可以为 \(0\) 次)。

由于答案可能非常大,请输出答案对 \(998244353\) 取模的结果。

题解

我们设 \(dp_{i,j,0/1}\) 表示走了 \(i\) 条边来到了 \(j\),然后经过 \(X\) 点的次数模 \(2\) 的值。

那么每次枚举所有节点,\(dp_{i, u, k} = \sum\limits_{(u, v) \in E} dp_{i-1, v, k} ~(u \neq X)\)

\(u = X\) 的时候改为 \(dp_{i-1,v,1-k}\) 就行了。注意初始状态 \(dp_{0,s,0} = 1\)

Code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int M = 2005;
const int mod = 998244353;
vector<int> vec[M];
int f[M][M][2];
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	int n, m, k, s, t, x;
	cin >> n >> m >> k >> s >> t >> x;
	for(int i = 1; i <= m; i++){
		int u, v;
		cin >> u >> v;
		vec[u].push_back(v);
		vec[v].push_back(u);
	}
	f[0][s][0] = 1;
	for(int i = 1; i <= k; i++){
		for(int j = 1; j <= n; j++){
			for(auto v : vec[j]){
				for(int r = 0; r <= 1; r++){
					if (j == x) f[i][j][r] = (f[i][j][r] + f[i - 1][v][1 - r]) % mod;
					else f[i][j][r] = (f[i][j][r] + f[i - 1][v][r]) % mod;
				}
			}
		}
	}
	cout << f[k][t][0];
	return 0;
}

AT_abc441_e [ABC441E] A > B substring

题目描述

给定一个由 ABC 三种字符组成的长度为 $ N $ 的字符串 $ S $。

$ S $ 的非空连续子串共有 $ \dfrac{N(N+1)}2 $ 个,请问其中包含的 A 数量多于 B 数量的子串有多少个?

请注意,即使两个子串内容相同,只要它们在 $ S $ 中取出的位置不同,就视为不同的子串。

子串 是指从 $ S $ 的开头删除 $ 0 $ 个或更多字符,以及从末尾删除 $ 0 $ 个或更多字符所得到的字符串。
例如,ABABC 的子串,但 AC 不是 ABC 的子串。

题解

我们将 A 视为 \(+1\)B 视为 \(-1\)C 视为 \(0\)。问题变成:求有多少个子串其区间和 \(> 0\)

在遍历的过程中,我们记录下当前的前缀和 \(s\),并且维护 \(dp_j\) 表示前缀和为 \(j\) 的位置的数量,\(cnt\) 为以当前位置结尾的合法子串数量。那么当前位置分情况讨论:

  • \(s_i = A\) 时,此时前缀和将会比原来多 \(1\),加上这一位后前缀和为 \(sum\) 的串也符合条件, \(cnt \leftarrow cnt + dp_{s}, ~s \leftarrow s + 1\)
  • \(s_i = B\) 时,此时前缀和将会比原来少 \(1\),加上这一位后前缀和为 \(sum\) 的串就不符合条件了, \(cnt \leftarrow cnt - dp_{s}, ~s \leftarrow s - 1\)
  • \(s_i = C\) 时,无事发生。

更新完后,将 \(s\) 计入数组 \((dp_s \leftarrow dp_s + 1)\),并把 \(cnt\) 累加进答案即可,时间复杂度 \(\mathcal{O}(n)\)

注意前缀和可能为负数,作为数组下标时要加上 \(n\)

Code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int M = 5e5 + 5;
int ans, cnt, sum, dp[M << 1];
signed main() {
	int n;
	string s;
	cin >> n >> s;
	s = " " + s;
	dp[n] = 1;
	for (int i = 1; i <= n; i++) {
		if (s[i] == 'A') cnt += dp[n + sum], sum++;
		else if(s[i] == 'B') cnt -= dp[n + sum - 1], sum--;
		dp[n + sum]++;
		ans += cnt;
	}
	cout << ans;
	return 0;
}

AT_arc182_a [ARC182A] Chmax Rush!

题目描述

有一个长度为 \(N\) 的整数序列 \(S\)。初始时,\(S\) 的所有元素均为 \(0\)

另外,给定长度为 \(Q\) 的整数序列 \(P=(P_1,P_2,\dots,P_Q)\)\(V=(V_1,V_2,\dots,V_Q)\)

すぬけ君想要对数列 \(S\) 顺次进行 \(Q\) 次操作。第 \(i\) 次操作如下:

  • 可以进行以下两种操作之一。
    • \(S_1,S_2,\dots,S_{P_i}\) 全部替换为 \(V_i\)。但如果在这次操作前,\(S_1,S_2,\dots,S_{P_i}\) 中存在严格大于 \(V_i\) 的元素,すぬけ君会哭出来。
    • \(S_{P_i},S_{P_i+1},\dots,S_N\) 全部替换为 \(V_i\)。但如果在这次操作前,\(S_{P_i},S_{P_i+1},\dots,S_N\) 中存在严格大于 \(V_i\) 的元素,すぬけ君会哭出来。

请计算,能够使すぬけ君不哭地完成 \(Q\) 次操作的操作序列有多少种,将答案对 \(998244353\) 取模。

这里,若存在某个 \(1\leq i\leq Q\),使得在第 \(i\) 次操作中选择的操作不同,则认为两个操作序列是不同的。

题解

我们注意到,当 \(i < j, V_i>V_j\)时,操作 \(i\) 会将某个区间全部改为 \(V_i\)。在操作 \(j\) 执行时,如果它的区间与 \(V_i\) 留下的区间有交集,且交集位置上的值 \(\geq V_i\),就会导致操作 \(j\) 失败。有以下三种情况:

  • \(P_i < P_j\):禁止「\(i\) 后缀、\(j\) 前缀」的组合。
  • \(P_i > P_j\):禁止「\(i\) 前缀、\(j\) 后缀」的组合。
  • \(P_i = P_j\):无论 \(i\) 选哪个方向,\(P_i\) 一定会被覆盖为 \(V_i\),所以 \(j\) 的两种选择均禁止。

所以我们令 \(f_{i, 0/1}\) 表示 \(i\) 点能否选择前缀和后缀,枚举 \(i<j, V_i > V_j\) 的所有对,按照上述规则判断,最后答案就是 \(\prod\limits_{i=1}^q (f_{i, 0} + f_{i, 1})\)

Code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int M = 5005;
int f[M][2], a[M], b[M];
int n, q, ans = 1;
const int mod = 998244353;
int main() {
	cin >> n >> q;
	for (int i = 1; i <= q; i++) cin >> a[i] >> b[i];
	for (int i = 1; i <= q; i++) {
		f[i][0] = f[i][1] = 1;
	}
	for (int i = 1; i <= q; i++) {
		for (int j = i + 1; j <= q; j++) {
			if (b[i] > b[j]) {
				if (a[j] > a[i]) f[i][1] = 0, f[j][0] = 0;
				if (a[j] < a[i]) f[i][0] = 0, f[j][1] = 0;
				if (a[i] == a[j]) f[j][0] = f[j][1] = 0;
			}
		}
	}
	for (int i = 1; i <= q; i++) {
		ans *= (f[i][0] + f[i][1]);
		ans %= mod;
	}
	cout << ans;
	return 0;
}

AT_abc080_c [ABC080C] Shopping Street

题目描述

Joisino计划要在商店街开一家店。

这家店在周一到周五的 \(5\) 个工作日都有营业,其中每个工作日又被划分成上午和下午 \(2\) 个时间段,也就是共有 \(10\) 个时间段。当然,至少要有 \(1\) 个时间段这家店营业。

商店街原来有 \(N\) 个店铺,从 \(1\)\(N\) 编号。

这些店铺的营业时间将以 \(F_{i,j,k}=1\) 的形式给出。如果 \(F_{i,j,k}=1\) ,第 \(i\) 家店将在第 \(j\) 天的第 \(k\) 个时间段营业。在这里,我们这样定义:第 \(1\) 天是周一,第 \(2\) 天是周二,第 \(3\) 天是周三,第 \(4\) 天是周四,第 \(5\) 天是周五。同样的,第 \(1\) 个时间段是上午,第 \(2\) 个时间段是下午。

\(c_i\) 为第 \(i\) 家店和 Joisino 的店同时营业的时间段数,则Joisino商店的收益将会是 \(P_{1,c1}+P_{2,c2}+...+P_{N,cN}\)

请决定Joisino在这 \(10\) 个时间段分别是否营业,并求出Joisino商店可能的最大收益,且保证它至少要有 \(1\) 个时间段营业。

题解

暴力枚举 \(2^{10}\) 种状态计算即可。

Code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int M = 105;
int f[M][12], p[M][12], st[M];
signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	int n;
	cin >> n;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= 10; j++) cin >> f[i][j];
	}
	for(int i = 1; i <= n; i++){
		for(int j = 0; j <= 10; j++) cin >> p[i][j];
	}
	int ans = -1e10;
	for(int msk = 1; msk < 1024; msk++){
		memset(st, 0, sizeof(st));
		for(int i = 1; i <= n; i++){
			for(int j = 1; j <= 10; j++){
				if(f[i][j] && (1 << j - 1) & msk) st[i] ++;
			}
		}
		int res = 0;
		for(int i = 1; i <= n; i++) res += p[i][st[i]];
		ans = max(ans, res);
	}
	cout << ans;
	return 0;
}

AT_abc420_e [ABC420E] Reachability Query

题目描述

给定一个有 \(N\) 个顶点且没有边的无向图。
顶点编号为 \(1,2,\dots,N\),初始时所有顶点均为白色。
你需要处理共 \(Q\) 个如下三种类型的操作:

  • 类型 \(1\):在顶点 \(u\)\(v\) 之间添加一条无向边。
  • 类型 \(2\):如果顶点 \(v\) 是白色,则将其变为黑色;如果是黑色,则将其变为白色。
  • 类型 \(3\):判断从顶点 \(v\) 出发,经过若干条边(可以为零条)是否能够到达某个黑色顶点;如果可以,输出 Yes,否则输出 No

题解

用并查集维护连通块,并且记录每个连通块内有多少个黑点即可。

Code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int M = 2e5 + 5;
int col[M], num[M], fa[M];
int find(int x) {
	return fa[x] == x ? x : fa[x] = find(fa[x]);
}
signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	int n, q;
	cin >> n >> q;
	for (int i = 1; i <= n; i++) fa[i] = i;
	while (q--) {
		int op, u;
		cin >> op >> u;
		if (op == 1) {
			int v;
			cin >> v;
			int f1 = find(u), f2 = find(v);
			if(f1 != f2) {
				fa[f2] = f1;
				num[f1] += num[f2];
			}
		} else if(op == 2) {
			if(col[u]) {
				col[u] = 0;
				num[find(u)] --;
			} else {
				col[u] = 1;
				num[find(u)] ++;
			}
		} else {
			if(num[find(u)] > 0) cout << "Yes\n";
			else cout << "No\n";
		}
	}
	return 0;
}
posted @ 2026-05-28 08:02  nick_zha  阅读(2)  评论(0)    收藏  举报