文章目录
AtCoder Beginner Contest 209
A - Counting
题意:
题解:
代码:
#include <bits/stdc++.h>
#define int long long
#define debug(x) cout << #x << " = " << x << endl;
using namespace std;
int const MAXN = 2e5 + 10;
int n, m, T, a[MAXN];
signed main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
cin >> n >> m;
cout << max(m - n + 1, (int)0) ;
return 0;
}
B - Can you buy them all?
题意:
题解:
代码:
#include <bits/stdc++.h>
#define int long long
#define debug(x) cout << #x << " = " << x << endl;
using namespace std;
int const MAXN = 2e5 + 10;
int n, m, T, a[MAXN];
signed main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
cin >> n >> m;
int sum = 0;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
if (!(i & 1)) a[i] -= 1;
sum += a[i];
}
if (sum <= m) puts("Yes");
else puts("No");
return 0;
}
C - Not Equal
题意:
题解:
代码:
#include <bits/stdc++.h>
#define int long long
#define debug(x) cout << #x << " = " << x << endl;
using namespace std;
int const MAXN = 2e5 + 10;
int n, m, T, a[MAXN], mod = 1e9 + 7;
signed main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
cin >> n;
for (int i = 1; i <= n; ++i) cin >> a[i];
sort(a + 1, a + 1 + n);
int res = 1;
for (int i = 1; i <= n; ++i) {
res = (res * max((a[i] - i + 1), (int)0)) % mod;
}
cout << res;
return 0;
}
D - Collision
题意: 一颗n个点的树,有q个询问,每次询问2个点a和b之间的距离的奇偶性。如果距离是奇数,打印road;否则,打印Town。 2 < = N < = 1 0 5 , 1 < = Q < = 1 0 5 2<=N<=10^5,1<=Q<=10^5 2<=N<=105,1<=Q<=105
题解:
法一:询问距离,直接lca,然后判断奇偶性
法二:因为只需要奇偶性,因此可以化简lca的过程。查看使用lca求距离的公式:dist[a] + dist[b] - 2 * dist[lca(a, b)],既然是模2,那么直接就算(dist[a] + dist[b]) % 2,所以其实我只需要算出(dist[a] + dist[b])即可,这样就跳过lca的步骤。
代码:
// lca版本
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
int f[N][20], d[N], dist[N]; // f[i][j]表示从i开始,往上走2^j步到达的点,d为深度,dist为距离
int e[N], ne[N], h[N], idx, w[N];
int T, n, m, t; // t为数的深度
queue<int> q;
void add(int a, int b, int c) {
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
// 预处理:得到每个点的深度,距离,f数组
void bfs() {
q.push(1); // 把根放入队列,注意这里有可能根不是1
d[1] = 1;
while (q.size()) {
int x = q.front();
q.pop();
for (int i = h[x]; i != -1; i = ne[i]) {
int y = e[i];
if (d[y]) continue;
d[y] = d[x] + 1; // 更新深度
dist[y] = dist[x] + w[i]; // 更新距离
// 进行dp更新
f[y][0] = x;
for (int j = 1; j <= t; ++j) {
f[y][j] = f[f[y][j - 1]][j - 1]; // 分两段处理
}
q.push(y);
}
}
}
// 查找x和y的最近公共祖先
int lca(int x, int y) {
if (d[x] > d[y]) swap(x, y); // 保证x的深度浅一点
for (int i = t; i >= 0; --i)
if (d[f[y][i]] >= d[x]) y = f[y][i]; // 让x和y到同一个深度
if (x == y) return x;
for (int i = t; i >= 0; --i) {// 让x和y之差一步就能相遇
if (f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
}
return f[x][0];
}
int main() {
// cin >> T;
T = 1;
while (T--) {
memset(h, -1, sizeof h);
idx = t = 0;
cin >> n >> m;
t = (int)(log(n) / log(2)) + 1; // 得到树的深度
// 读入一棵树
for (int i = 0; i < n - 1; ++i) {
int a, b, c;
scanf("%d %d", &a, &b);
add(a, b, 1), add(b, a, 1);
}
bfs();
// 回答询问
for (int i = 1; i <= m; ++i) {
int a, b;
scanf("%d %d", &a, &b);
int cur_dist = dist[a] + dist[b] - 2 * dist[lca(a, b)];
if (cur_dist & 1) puts("Road");
else puts("Town");
}
}
return 0;
}
// 模2版本
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
int d[N]; // f[i][j]表示从i开始,往上走2^j步到达的点,d为深度,dist为距离
int e[N], ne[N], h[N], idx, w[N];
int T, n, m, t; // t为数的深度
queue<int> q;
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
// 预处理:得到每个点的深度,距离,f数组
void bfs() {
q.push(1); // 把根放入队列,注意这里有可能根不是1
d[1] = 1;
while (q.size()) {
int x = q.front();
q.pop();
for (int i = h[x]; i != -1; i = ne[i]) {
int y = e[i];
if (d[y]) continue;
d[y] = d[x] + 1; // 更新深度
q.push(y);
}
}
}
int main() {
// cin >> T;
T = 1;
while (T--) {
memset(h, -1, sizeof h);
idx = t = 0;
cin >> n >> m;
t = (int)(log(n) / log(2)) + 1; // 得到树的深度
// 读入一棵树
for (int i = 0; i < n - 1; ++i) {
int a, b, c;
scanf("%d %d", &a, &b);
add(a, b), add(b, a);
}
bfs();
// 回答询问
for (int i = 1; i <= m; ++i) {
int a, b;
scanf("%d %d", &a, &b);
int cur_dist = (d[a] + d[b]) % 2;
if (cur_dist & 1) puts("Road");
else puts("Town");
}
}
return 0;
}
E - Shiritori
题意: 两个人玩成语接龙,如果一个人选择了单词A,那么下一个人只能选择单词B,同时保证单词A的最后三个字符和单词B的头三个字符相同,如果当前人无法继续接单词则失败。现在给定n个单词,每个单词长度在5 ~ 8之间,每次玩接龙只能在给定的单词内选择使用,同一个单词可以被多次使用。现在要求打印i个结果,问使用第i个单词开始进行接龙游戏,是先手必胜还是先手必败,还是平局。如果先手必胜,打印Aoki;先手必败,打印Takahashi;平局打印Draw。 1 < = n < = 2 e 5 1<=n<=2e5 1<=n<=2e5
题解: 拓扑排序。首先明确一个点,如果一个单词后面无法续上其他单词,那么这个单词就是必败态;如果一个单词后面存在必败态,那么这个单词就是必胜态;如果一个单词的后继状态全是必胜态,那么这个单词也是必败态。
因此,我们可以首先找出必败态单词作为初始起点,倒着找它的前驱去进行更新。即找到出度为0的点作为初始状态,这个看起来非常像拓扑排序(拓扑排序是找入度为0的点作为初始状态)。后面更新就按照类似拓扑排序的方式进行即可。
但是这个题目存在平局,怎么处理这个问题?我们只需要保证队列里面只放置必胜态或者必败态的点,那么每次更新都是拿必胜态或者必败态去更新,这样使得deg[u]–只可能是必败态或者必胜态。如果一个点的deg[u]为0,且还不是必胜态,那么它必然是必败态。
最后打印的时候,如果一个点是必败态,就打印先手必败;是必胜态,就打印先手必胜;如果都不是,那就是平局。
同时还需要考虑一个建图问题,如果暴力建图就是 O ( n 2 ) O(n^2) O(n2),因此需要优化,每三个字符就优化为一个点,然后每个单词都优化为一条边,这样建图就是 O ( n ) O(n) O(n),这种建图方式类似这题:https://www.acwing.com/problem/content/1167/
代码:
#include <bits/stdc++.h>
using namespace std;
int const MAXN = 4e5 + 10;
int n, idx, deg[MAXN];
char str[MAXN][10];
bool win[MAXN], fail[MAXN];
queue<int> q;
vector<int> from[MAXN];
map<int, int> mp;
int Hash(char s1, char s2, char s3) {
int tmp = (s1 - 'A' + 1) * 100 * 100 + (s2 - 'A' + 1) * 100 + (s3 - 'A' + 1);
if (!mp[tmp]) mp[tmp] = ++idx;
return mp[tmp];
}
signed main() {
cin >> n;
for (int i = 1; i <= n; i++) {
scanf("%s", str[i] + 1);
int len = strlen(str[i] + 1);
int u = Hash(str[i][1], str[i][2], str[i][3]), v = Hash(str[i][len - 2], str[i][len - 1], str[i][len]);
from[v].push_back(u); // 记录前驱
deg[u]++; // 出度
}
for (int i = 1; i <= idx; i++)
if (!deg[i]) fail[i] = 1, q.push(i);
while (!q.empty()) {
int u = q.front();
q.pop();
for (int i = 0; i < from[u].size(); i++) {
int v = from[u][i]; // 找前驱
deg[v]--;
if (fail[u] && !win[v]) win[v] = 1, q.push(v); // 后继是必败他,那么就是必胜态
if (!deg[v] && !win[v]) fail[v] = 1, q.push(v); // 后继全部更新完,还不是必胜态,那么就是必败态
}
}
for (int i = 1; i <= n; i++) {
int len = strlen(str[i] + 1);
int v = Hash(str[i][len - 2], str[i][len - 1], str[i][len]);
if (win[v]) puts("Aoki");
else if (fail[v]) puts("Takahashi");
else puts("Draw");
}
return 0;
}
F - Deforestation
题意: 砍树。
题解: dp。想不明白别人的转移,鸽了趴,哪天半夜睡不着再来看看。
代码: