2146 字
11 分钟
abc456
NOTE

比赛链接:Atcoder Beginner Contest 456

A - Dice#

关键词:签到

思路#

显然三个骰子可以扔出的点数满足 3x183 \leqslant x \leqslant 18

Code#

// Problem: AT ABC456 A
// Contest: AtCoder - AtCoder Beginner Contest 456
// URL: https://atcoder.jp/contests/abc456/tasks/abc456_a
// Time: 2026-05-03 12:12:03
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<double, double> pdd;
typedef pair<long long, long long> pll;
const int dx[] = {-1, 0, 1, 0, -1, 1, 1, -1};
const int dy[] = {0, 1, 0, -1, 1, 1, -1, -1};
const int inf = 0x3f3f3f3f;
const int N = 0;
int main()
{
int x;
cin >> x;
if (x >= 3 && x <= 18)
cout << "Yes" << endl;
else
cout << "No" << endl;
return 0;
}

B - 456#

关键词:暴力,签到

思路#

总共有 216 种情况,暴力枚举判断即可。

Code#

// Problem: AT ABC456 B
// Contest: AtCoder - AtCoder Beginner Contest 456
// URL: https://atcoder.jp/contests/abc456/tasks/abc456_b
// Time: 2026-05-03 12:12:04
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<double, double> pdd;
typedef pair<long long, long long> pll;
const int dx[] = {-1, 0, 1, 0, -1, 1, 1, -1};
const int dy[] = {0, 1, 0, -1, 1, 1, -1, -1};
const int inf = 0x3f3f3f3f;
const int N = 6;
int a[3][N];
int main()
{
for (int i = 0; i < 3; i++)
for (int j = 0; j < 6; j++)
cin >> a[i][j];
int res = 0;
for (int i = 0; i < 6; i++)
for (int j = 0; j < 6; j++)
for (int k = 0; k < 6; k++)
{
if ((a[0][i] == 4 && a[1][j] == 5 && a[2][k] == 6) || (a[0][i] == 4 && a[1][j] == 6 && a[2][k] == 5) ||
(a[0][i] == 5 && a[1][j] == 4 && a[2][k] == 6) || (a[0][i] == 5 && a[1][j] == 6 && a[2][k] == 4) ||
(a[0][i] == 6 && a[1][j] == 4 && a[2][k] == 5) || (a[0][i] == 6 && a[1][j] == 5 && a[2][k] == 4))
res++;
}
cout << fixed << setprecision(10) << 1.0 * res / 216 << endl;
return 0;
}

C - Not Adjacent#

关键词:思维、模拟

思路#

如果存在 Si=Si+1S_i=S_{i+1} 的位置,这两个字符不能同时选取,因此答案可以通过在第 ii 个与第 i+1i+1 个字符之间分割,计算每个块的数量并求和得到。

If S = abbcabcbaaabc
ab|bcabcba|a|abc
3 + 28 + 1 + 6 = 38

Code#

// Problem: AT ABC456 C
// Contest: AtCoder - AtCoder Beginner Contest 456
// URL: https://atcoder.jp/contests/abc456/tasks/abc456_c
// Time: 2026-05-03 12:12:05
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<double, double> pdd;
typedef pair<long long, long long> pll;
const int dx[] = {-1, 0, 1, 0, -1, 1, 1, -1};
const int dy[] = {0, 1, 0, -1, 1, 1, -1, -1};
const int inf = 0x3f3f3f3f;
const int N = 0;
const int mod = 998244353;
int main()
{
string s;
cin >> s;
int n = (int) s.size();
ll res = 0, idx = 0;
for (int i = 0; i < n; i++)
{
if (s[i] == s[i + 1] || i + 1 == n)
{
res += (ll) (i - idx + 1) * (i - idx + 2) / 2;
idx = i + 1;
}
}
cout << res % mod << endl;
return 0;
}

D - Not Adjacent 2#

关键词:DP,统计子序列

思路#

在构建子序列时,当我们想要把当前字符(比如 a)加入到某个现有的子序列末尾时,我们只关心这个现有的子序列,最后一个字符是什么。这就提示我们,必须把 DP 的状态按照“以什么字符结尾”来进行分类。

因为字符集只有 a, b, c 三个字符,我们可以只维护三个变量:

  • dp[a]dp[a]:表示目前为止,以字符 a 结尾的合法子序列数量。
  • dp[b]dp[b]:表示目前为止,以字符 b 结尾的合法子序列数量。
  • dp[c]dp[c]:表示目前为止,以字符 c 结尾的合法子序列数量。

假设我们现在从左到右遍历字符串,当前遇到了一个字符 a,我们该如何更新这三个变量呢?

对于以 a 结尾的子序列,它的来源有四部分:

  1. 旧的序列:之前已经构成的以 a 结尾的子序列。因为我们是“子序列”提取,我们可以选择不把当前这个 a 纳入进来,所以原来的 dp[a]dp[a] 个子序列依然存在且合法。
  2. 自立门户:当前这个字符 a 自己单独组成一个长度为 1 的子序列 "a"。这贡献了 11 个。
  3. 接在 b 后面:把当前字符 a 接在所有合法的、以 b 结尾的子序列后面,构成了新的以 a 结尾的子序列。这贡献了 dp[b]dp[b] 个。
  4. 接在 c 后面:把当前字符 a 接在所有合法的、以 c 结尾的子序列后面。这贡献了 dp[c]dp[c] 个。

把它们加起来,遇到字符 a 时,新的 dp[a]dp[a] 的状态转移方程就是:

dpnew[a]=dpold[a]+1+dpold[b]+dpold[c]dp_{new}[a] = dp_{old}[a] + 1 + dp_{old}[b] + dp_{old}[c]

而此时,因为当前字符是 a,它并没有产生新的以 bc 结尾的子序列,所以有:

dpnew[b]=dpold[b]dp_{new}[b] = dp_{old}[b]dpnew[c]=dpold[c]dp_{new}[c] = dp_{old}[c]

同理,如果遇到了字符 bc,只需要把上面的公式替换一下字母即可。

Code#

// Problem: AT ABC456 D
// Contest: AtCoder - AtCoder Beginner Contest 456
// URL: https://atcoder.jp/contests/abc456/tasks/abc456_d
// Time: 2026-05-03 12:12:06
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<double, double> pdd;
typedef pair<long long, long long> pll;
const int dx[] = {-1, 0, 1, 0, -1, 1, 1, -1};
const int dy[] = {0, 1, 0, -1, 1, 1, -1, -1};
const int inf = 0x3f3f3f3f;
const int N = 0;
const int mod = 998244353;
ll res;
int main()
{
string s;
cin >> s;
ll dp[3] = {0, 0, 0};
for (char c: s)
{
if (c == 'a')
dp[0] = (dp[0] + dp[1] + dp[2] + 1) % mod;
else if (c == 'b')
dp[1] = (dp[0] + dp[1] + dp[2] + 1) % mod;
else
dp[2] = (dp[0] + dp[1] + dp[2] + 1) % mod;
}
ll res = (dp[0] + dp[1] + dp[2]) % mod;
cout << res << endl;
return 0;
}

E - Endless Holidays#

关键词:思维,图论,判断环

思路#

TIP

由于可以选择留在当前的城市,所以在建图时需要把每个点自身加进图里(自环)。

既然 Takahashi 每天都在移动或停留,我们可以用一个二元组 (u, d) 来表示他当前的状态:u 表示当前所在的城市,d 表示当前是星期几(为便于取模从 0 开始)。考虑这个状态图中的有效状态与转移过程:

  • 有效状态:只有当 S[u][d] == 'o' 时,这个状态才是合法的(因为必须是节假日)。
  • 状态转移:如果高桥今天在 (u, d),明天他可以去任何一个相邻的城市 v(或者留在 u)。那么就会有一条从 (u, d) 指向 (v, (d + 1) % W) 的有向边。当然,前提是目标状态也必须是合法的。

假如他想要永远移动下去,就意味着必须要找一条无限长的路径,也就是图中存在环

NOTE

怎么判断图里有没有环?我们可以用一种“反向拓扑排序”思想:不断剪除死胡同

  1. 我们统计每个合法状态的出度,即它能走到多少个合法的明天状态。
  2. 如果某个状态的出度为 0,说明它是个死胡同,走到这里就结束了,我们把它从图里删掉。
  3. 删除它之后,可能会导致指向它的那些状态也变成死胡同(出度减 1)。如果它们出度也变成了 0,就继续删。
  4. 如果我们能把图里所有的有效状态都删光,说明处处都是死胡同,输出 No
  5. 如果最后还有状态没被删掉,说明它们形成了环,输出 Yes

Code#

// Problem: AT ABC456 E
// Contest: AtCoder - AtCoder Beginner Contest 456
// URL: https://atcoder.jp/contests/abc456/tasks/abc456_e
// Time: 2026-05-03 12:12:07
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<double, double> pdd;
typedef pair<long long, long long> pll;
const int dx[] = {-1, 0, 1, 0, -1, 1, 1, -1};
const int dy[] = {0, 1, 0, -1, 1, 1, -1, -1};
const int inf = 0x3f3f3f3f;
const int N = 1e5 + 10;
void solve()
{
int n, m, w;
cin >> n >> m;
vector<vector<int>> adj(n + 1);
for (int i = 0; i < m; i++)
{
int u, v;
cin >> u >> v;
adj[u].push_back(v), adj[v].push_back(u);
}
for (int i = 1; i <= n; i++)
adj[i].push_back(i);
cin >> w;
vector<string> s(n + 1);
for (int i = 1; i <= n; i++)
cin >> s[i];
// out[u][d] 表示状态 (u, d) 有多少个合法的下一个状态
vector<vector<int>> out(n + 1, vector<int>(w, 0));
int valid_states = 0;
for (int u = 1; u <= n; u++)
for (int d = 0; d < w; d++)
if (s[u][d] == 'o')
{
valid_states++;
int nxt = (d + 1) % w;
for (int v: adj[u])
{
if (s[v][nxt] == 'o')
out[u][d]++;
}
}
queue<pii> q;
for (int u = 1; u <= n; u++)
for (int d = 0; d < w; d++)
if (s[u][d] == 'o' && out[u][d] == 0)
q.push({u, d});
int removed = 0;
while (!q.empty())
{
auto [u, d] = q.front();
q.pop();
removed++;
int prev_d = (d - 1 + w) % w;
for (int v: adj[u])
{
if (s[v][prev_d] == 'o')
{
out[v][prev_d]--;
if (out[v][prev_d] == 0)
q.push({v, prev_d});
}
}
}
if (removed < valid_states)
cout << "Yes" << endl;
else
cout << "No" << endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int T;
cin >> T;
while (T--)
solve();
return 0;
}