[题解]AtCoder Beginner Contest 226 题解
#A - Round decimals
Time Limit: 2s | Memory Limit: 1024MB | 题目连接
#题意简述
给定正实数 \(X(0\leq X\leq 100)\),输出距离 \(X\) 最近的整数。
#大体思路
直接四舍五入即可。
#Code
double x;
int main() {
scanf("%lf", &x);
printf("%d", (int)(x + 0.5));
return 0;
}
#B - Counting Arrays
Time Limit: 2s | Memory Limit: 1024MB | 题目连接
#题意简述
给定 \(n(n\leq2\cdot10^5)\) 个有序序列,序列 \(i\) 有 \(l_i(\sum l_i\leq2\times10^5)\) 个正整数,问有多少不同的序列。
#大体思路
直接当成字符串进行哈希即可,由于字符集较大,采用双模数哈希保证正确性。
#Code
#define mp(a, b) make_pair(a, b)
const int N = 200010;
const int BASE1 = 131;
const int BASE2 = 13331;
const int MOD2 = 998244353;
const int MOD1 = 19270817;
const int INF = 0x3fffffff;
template <typename T> inline void read(T &x) {
x = 0; int f = 1; char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
int n, l[N], cnt;
map <pair <int, int>, bool> m[N];
int main() {
read(n);
for (int i = 1; i <= n; ++ i) {
read(l[i]); int k1 = 0, k2 = 0, x = 0;
for (int j = 1; j <= l[i]; ++ j) {
read(x);
k1 = (1ll * k1 * BASE1 + x) % MOD1;
k2 = (1ll * k2 * BASE2 + x) % MOD2;
}
if (!m[l[i]][mp(k1, k2)])
++ cnt, m[l[i]][(mp(k1, k2))] = 1;
}
printf("%d", cnt); return 0;
}
#C - Martial artist
Time Limit: 2s | Memory Limit: 1024MB | 题目连接
#题意简述
\(n(n\leq2\cdot10^5)\) 个节点,节点 \(i\) 有代价 \(W_i\) 和 \(K_i(\sum K_i\leq2\cdot10^5)\) 个前置节点 \(A_{i,1}\dots A_{i,k_i}(A_{i,j}< i)\),选择 \(i\) 时 \(i\) 的前置节点必须都选择,问选择 \(n\) 号节点总共至少付出多少代价。
#大体思路
一张 DAG,注意到 \(A_{i,j}<i\),于是可以考虑从 \(n\) 开始倒序加入选取的节点,然后将该节点的前置节点全部加入选择序列,同时统计答案即可。
#Code
#define LOCAL
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 200010;
const int INF = 0x3fffffff;
template <typename T> inline void read(T &x) {
x = 0; int f = 1; char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
int n, k, vis[N]; ll f[N], ans;
vector <int> pre[N];
int main() {
read(n);
for (int i = 1; i <= n; ++ i) {
read(f[i]), read(k);
for (int j = 1, x; j <= k; ++ j)
read(x), pre[i].push_back(x);
}
vis[n] = 1;
for (int i = n; i >= 1; -- i) if (vis[i]) {
ans += f[i]; for (auto x : pre[i]) vis[x] = 1;
}
printf("%lld", ans); return 0;
}
#D - Teleportation
Time Limit: 2s | Memory Limit: 1024MB | 题目连接
#题意简述
在二维平面上有 \(n(n\leq500)\) 个点 \((a_i,b_i)(0\leq a_i,b_i\leq10^9)\),规定移动方式 \((x,y)\) 为从 \((a,b)\) 移动到 \((a+x,b+y)\),从点 \((a_i,b_i)\) 移动到 \((a_j,b_j)\) 仅能选取一种移动方式并反复使用,问使任意两个点可达至少需要多少中不同的移动方式。
#大体思路
注意到得到的移动方式 \((x,y)\) 一定有 \(x\) 和 \(y\) 互质,否则一定可以共同除以最大公因数得到 \((x',y')\),最终的总个数不会增多,于是我们暴力枚举任意两个点 \(i\) 和 \(j\),求出符合要求的 \((x,y)\) 并用 map
去重即可。
#Code
#define mp(a, b) make_pair(a, b)
const int N = 100010;
const int INF = 0x3fffffff;
template <typename T> inline void read(T &x) {
x = 0; int f = 1; char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> inline T Abs(const T &x) {return x < 0 ? -x : x;}
struct Town {int x, y;} a[N];
int n, cnt;
map <pair <int, int>, bool> m;
int gcd(int x, int y) {return y ? gcd(y, x % y) : x;}
int main() {
read(n);
for (int i = 1; i <= n; ++ i)
read(a[i].x), read(a[i].y);
for (int i = 1; i <= n; ++ i)
for (int j = 1; j <= n; ++ j) {
if (i == j) continue;
int dx = a[i].x - a[j].x;
int dy = a[i].y - a[j].y;
int g = gcd(Abs(dx), Abs(dy));
if (!m[mp(dx / g, dy / g)])
++ cnt, m[mp(dx / g, dy / g)] = 1;
}
printf("%d", cnt); return 0;
}
#E - Just One
Time Limit: 2s | Memory Limit: 1024MB | 题目连接
#题意简述
给定一个 \(n(n\leq2\cdot10^5)\) 个点 \(m(m\leq2\cdot10^5)\) 条边的无向图(无重边、自环),现在每个点需要选定一条相邻的边定向为出边,每条边需要恰好被定向一次,问有多少种定向方法,对 \(998244353\) 取模。
#大体思路
注意到一个 \(k\) 个点的连通块中应当恰好有 \(k\) 条边被定向,于是显然只有一棵基环树可以有合法的定向方式,且恰好有两种,于是我们考虑每一个连通块,通过统计该连通块中点、边的个数判断该连通块是否是基环树,是则给答案乘上 \(2\),否则答案为 \(0\).
#Code
const int N = 500010;
const int MOD = 998244353;
const int INF = 0x3fffffff;
template <typename T> inline void read(T &x) {
x = 0; int f = 1; char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
struct Edge {int u, v, nxt;} e[N];
int ecnt(1), head[N], d[N], n, m, vis[N], p_num, e_num, ans;
inline void add_edge(int u, int v) {
e[ecnt].u = u, e[ecnt].v = v, ++ d[u];
e[ecnt].nxt = head[u], head[u] = ecnt ++;
}
void dfs(int x) {
vis[x] = 1; e_num += d[x]; ++ p_num;
for (int i = head[x]; i; i = e[i].nxt)
if (!vis[e[i].v]) dfs(e[i].v);
}
int main() {
read(n), read(m); ans = 1;
for (int i = 1; i <= m; ++ i) {
int u, v; read(u), read(v);
add_edge(u, v), add_edge(v, u);
}
for (int i = 1; i <= n; ++ i) if (!vis[i]) {
p_num = 0, e_num = 0; dfs(i);
if (e_num == (p_num << 1)) (ans *= 2) %= MOD;
else {ans = 0; break;}
}
printf("%d", ans); return 0;
}
#F - Score of Permutations
Time Limit: 2s | Memory Limit: 1024MB | 题目连接
#题意简述
对于 \(1,2,\dots,n(n\leq50)\) 的一个排列 \(P=(p_1,p_2,\dots,p_n)\),他的价值 \(S(P)\) 为:
- 数列 \(T\) 最初为 \(t_i=i\),如下变换的次数即为 \(P\) 的价值,一次变化如下:对于 \(i\ne p_i\) 的 \(t_i\),令 \(t_{p_i}=t_i\),所有数字变化同时发生,在至少一次变换后,如果 \(t_i=i\),那么变换结束。
设 \(S_n\) 为 \(n\) 的所有排列的集合,给定 \(k\),求
#大体思路
首先考虑如何快速计算一个排列 \(P\) 的价值,不难发现所有的 \(p_i\) 会形成若干个环,显然变化总次数(\(P\) 的价值)为所有环长的 LCM,于是我们发现一个排列的价值与仅与该排列形成的环的个数及每个环的长度有关,不难发现所有环的环长恰好是 \(n\) 的一组划分数,由于 \(50\) 的划分数仅有 \(204226\) 种,每种不同的划分数的价值是容易得到的,下面考虑对于每种划分数出现的次数进行计数。
我们先考虑划分段,然后再考虑将划分得到的段连接成环。
考虑枚举 \(n\) 的全排列,共有 \(n!\) 种,现在将其划分为 \(m\) 段,每段的长度为 \(l_i\),我们可以将其简记为有 \(f_i\) 个长度为 \(l_i\) 的段,设共有 \(m\) 种不同的段长,于是考虑其中的每一段,对于相同的 \(l_i\) 个数,显然被计数了 \(l_i!\) 次,如 \(12|456\),后面的 \(456\) 在 \(n\) 的全排列中会被计数 \(3!=6\) 次,\(12\) 则会被计数 \(2!=2\) 次,同时注意到 \(f_i\) 个长度为 \(l_i\) 的段之间是没有区别/顺序的,于是我们还需要除以 \(f_i!\),于是我们得到了将 \(n\) 划分为 \(f_i,l_i\) 对应段划分的所有方案,为
然后,我们考虑每个段划分如何连成一个环,也就是长度为 \(l_i\) 的环排列数,为 \((l_i-1)!\),于是我们得到了一组划分的所有情况数
由于 \(50\) 的划分数只有 \(204226\) 种,所以我们直接 DFS 枚举划分数,同时维护对应的价值和对应的数量即可。
#Code
const int N = 110;
const int MOD = 998244353;
const int INF = 0x3fffffff;
template <typename T> inline void read(T &x) {
x = 0; int f = 1; char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
int n, k, fac[N], fac_p[N][N], res, inv_fac[N], inv_fac_p[N][N], ans;
inline void madd(int &x, int y) {x = x + y < MOD ? x + y : x + y - MOD;}
int gcd(int x, int y) {return y ? gcd(y, x % y) : x;}
inline int lcm(int x, int y) {return (1ll * x * y / gcd(x, y)) % MOD;}
inline int fpow(int x, int y) {
int res = 1;
while (y) {
if (y & 1) res = 1ll * res * x % MOD;
y >>= 1; x = 1ll * x * x % MOD;
}
return res;
}
inline void calc(int val, int num) {
val = fpow(val, k);
madd(ans, 1ll * val * num % MOD);
}
void dfs(int rst, int lmt, int val, int num) {
if (!rst) {calc(val, num); return;} if (rst < lmt) return;
for (int i = lmt; i <= rst; ++ i)
for (int j = 1; i * j <= rst; ++ j) {
int new_num = 1ll * num * inv_fac_p[i][j] % MOD;
new_num = 1ll * new_num * inv_fac[j] % MOD * fac_p[i - 1][j] % MOD;
dfs(rst - i * j, i + 1, lcm(val, i), new_num);
}
}
int main() {
read(n), read(k); fac[0] = 1;
for (int i = 1; i <= n; ++ i)
fac[i] = 1ll * fac[i - 1] * i % MOD;
for (int i = 0; i <= n; ++ i)
for (int j = 0; j <= n; ++ j)
fac_p[i][j] = fpow(fac[i], j);
for (int i = 0; i <= n; ++ i)
inv_fac[i] = fpow(fac[i], MOD - 2);
for (int i = 0; i <= n; ++ i)
for (int j = 0; j <= n; ++ j)
inv_fac_p[i][j] = fpow(fac_p[i][j], MOD - 2);
dfs(n, 1, 1, fac[n]); printf("%d", ans); return 0;
}
#G - The baggage
Time Limit: 2s | Memory Limit: 1024MB | 题目连接
#题意简述
有 \(a_i(1\leq i\leq5)\) 个重量为 \(i\) 的包裹,和 \(b_i(1\leq a_i\leq5)\) 个力量为 \(i\) 的人,每个人可以拿总重不超过自己力量上限的任意个包裹,问能否将所有的包裹都拿起。
#大体思路
不知道这题为什么放在 G 题,明明就是个极为简单的贪心/yiw
首先不难想到我们一定是从大至小地考虑每个包裹,因为重量小的包裹可行的组合数更多,限制要更少,于是我们从大到小考虑每个包裹。
首先重量为 \(5\) 的包裹显然只能让力量为 \(5\) 的人拿,重量为 \(4\) 的包裹可以让力量为 \(4\) 或 \(5\) 的人拿,如果让 \(4\) 的人先拿,那么剩下的 \(5\) 可以有组合 \(2,2,1\) 或 \(3,2\),如果让 \(5\) 先拿,那么结合省下的 \(4\) 只剩下组合 \(2,2,1\),显然让 \(4\) 先拿更优。
然后考虑 \(3\) 以怎样的顺序进行,
以同样的方式不难证明,重量 \(3\) 优先选力量 \(3\) 一定不会更差;然后考虑如果再选 \(4\),那么与剩下的 \(5\) 组合有 \(1,2,3\) 和 \(1,1,2,2\),如果选 \(5\),那么与剩下的 \(4\) 组合有 \(2,2,2\) 和 \(1,1,2,2\) 和 \(1,2,3\),显然先选 \(5\) 剩下的可行组合方式、可兼容的 \(2\) 更多,于是重量 \(3\) 的选择顺序一定是 \(3,5,4\).
最后 \(2\) 和 \(1\) 的选择顺序都是从大到小榨取剩余人的剩余价值即可。
注意到为了保证组合的正确,我们每次让力量为 \(x\) 的人拿重量 \(y\) 的包裹仅拿 \(1\) 个,然后将它加入到 \(x-y\) 力量的人中即可。
原题题解好像给了一个极其严谨的证明,有兴趣的话可以去看看。
(OI 不需要证明;贪心,狗都不证!(狗头保命))
#Code
#define ll long long
const int N = 100010;
const int INF = 0x3fffffff;
template <typename T> inline void read(T &x) {
x = 0; int f = 1; char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
int t; ll a[6], b[6];
inline void pick(int x, int y) {
ll z = min(a[x], b[y]);
a[x] -= z, b[y] -= z, b[y - x] += z;
}
void MAIN() {
for (int i = 1; i <= 5; ++ i) read(a[i]);
for (int i = 1; i <= 5; ++ i) read(b[i]);
a[0] = b[0] = 0;
pick(5, 5); pick(4, 4); pick(4, 5);
pick(3, 3); pick(3, 5); pick(3, 4);
for (int i = 5; i >= 2; -- i) pick(2, i);
for (int i = 5; i >= 1; -- i) pick(1, i);
for (int i = 1; i <= 5; ++ i)
if (a[i]) {puts("No"); return;}
puts("Yes");
}
int main() {read(t); while (t --) MAIN(); return 0;}
#H - Random Kth Max
Time Limit: 2s | Memory Limit: 1024MB | 题目连接
#题意简述
有 \(n(n\leq10^5)\) 个连续随机变量 \(a_i\) ,每个变量 \(a_i\) 有一个取值范围 \([l_i,r_i](0\leq l_i<r_r\leq100)\),\(a_i\) 等概率取到该区间内的任何数,为这 \(a_i\) 个数中的期望第 \(k\) 大,答案对 \(998244353\) 取模。
#大体思路
不会,咕着~
#总结
感觉这场 ABC 除了 H 以外也就是普及组水平,但是 100 min 写完雀食颇具难度,wtcl /kk