刷Atcoder 二周目
AT_arc166_a [ARC166A] Replace C or Swap AB
题目描述
给定由 A、B、C 组成的长度为 \(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\) 个人依次进行如下操作:
- 如果自己左侧或右侧还有勺子,则从中取走一把。
- 如果自己两侧的勺子都还在,则取走与自己惯用手相同侧的勺子。
- 否则什么也不做。
给定一个由 L、R、? 组成的长度为 \(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
题目描述
给定一个由 A、B、C 三种字符组成的长度为 $ N $ 的字符串 $ S $。
$ S $ 的非空连续子串共有 $ \dfrac{N(N+1)}2 $ 个,请问其中包含的 A 数量多于 B 数量的子串有多少个?
请注意,即使两个子串内容相同,只要它们在 $ S $ 中取出的位置不同,就视为不同的子串。
子串 是指从 $ S $ 的开头删除 $ 0 $ 个或更多字符,以及从末尾删除 $ 0 $ 个或更多字符所得到的字符串。
例如,AB 是 ABC 的子串,但 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;
}

浙公网安备 33010602011771号