AtCoder Beginner Contest 243
AtCoder Beginner Contest 243 Solution
A - Shampoo
题意
牙膏的体积为 V,F
M
T
三人每天需要用的牙膏量为 A,B,C,并且每天使用牙膏的顺序都是F
M
T
,输出若干天后,哪个人会首先没有充足的牙膏用。
数据范围 : 1 \leq V,A,B,C \leq 10^5
题解
考虑直接模拟牙膏每天的使用情况,复杂度O(\frac{V}{A+B+C})。
C++代码示例
# include <bits/stdc++.h>
using namespace std;
int main() {
int v,a,b,c; cin>>v>>a>>b>>c;
int now = 0;
while (v >= 0) {
if (now == 0) {
v-=a; if (v < 0) puts("F");
}else if (now == 1) {
v-=b; if (v < 0) puts("M");
}else if (now == 2) {
v-=c; if (v < 0) puts("T");
}
now=(now+1)%3;
}
return 0;
}
B - Hit and Blow
题意
有长度为 n 的两个数组 a,b ,输出两个数:
- 第 1 个数:有多少个 i(1 \leq i \leq n) 满足 a_i = b_i。
- 第 2 个数:有多少对(i,j) (i \ne j, 1 \leq i,j \leq n) 满足 a_i = b_j。
数据范围:1 \leq n \leq 10^3, 1 \leq a_i,b_i \leq 10^9,满足 a,b 数组内部元素互不相同。
题解
对于第 1 个数,可以用 O(n) 的线性遍历实现。
对于第 2 个数,显然有一个 O(n^2) 的暴力做法,直接模拟即可。
\text{Bonus} : 对于第 2 个数,有一个 O(n \log_2 n) 的做法。
用 set 将 a 中的元素存入,遍历 b 中的所有元素,如果发现 a_i = b_i 则累加 ans_1,否则看 set 中是否有一个值为 b_i 的元素 ,如果有则累加 ans_2。
本做法如果将 set 改为 map,可以处理“取消限制 a,b 数组内部元素互不相同”的情况。
时间复杂度 O(n \log_2 n)
C++ 代码示例
# include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;
int a[N], b[N];
int main() {
int n; cin >> n;
for (int i = 1 ; i <= n ; i++) {
cin >> a[i];
}
set<int>s;
for (int i = 1 ; i <= n ; i++) {
cin >> b[i];
s.insert(b[i]);
}
int ans1 = 0, ans2 = 0;
for (int i = 1; i <= n; i++) {
if (a[i] == b[i]) ans1++;
else if (s.find(a[i]) != s.end()) ans2++;
}
cout << ans1 << endl <<ans2 << endl;
return 0;
}
C - Collision 2
题意
n 个人,第 i(1 \leq i \leq n) 个人的位置在 (x_i,y_i) 处。每个人有一个面朝方向,没有两个人在同一位置。
这个方向由字符串 s 给出,s_i = L 表示面朝水平左,否则面朝水平右。
如果这 n 个人保持面朝的方向匀速走,会发生碰撞输出 Yes
否则输出 No
。
数据范围:2 \leq n \leq 2\times 10^5 , 0 \leq x_i,y_i \leq 10^9, |s| = n。
题解
首先,可以发现不在同一条水平线上的人不会相互影响。
因此,可以用一个map<int, vector<int>>
将不同 y 值的人,分成不同的组。
问题转化为在一条水平线上,有一些人,坐标互不相同,面朝左右,相同速度移动是否相碰。
那么将这些人按照 x 坐标排序。
容易发现,如果在排序后的人所面朝的L/R序列中有两相邻的字符是RL,那么必然会相碰。
并且这个条件是一个充分必要条件。
时间复杂度为 O(n \log_2 n)
C++代码示例
# include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
struct rec{
int x, y;
}a[N];
string s;
map<int, vector<pair<int, int>>>v;
int main() {
int n; cin >> n;
for (int i = 0; i < n; i++) {
cin >> a[i].x >> a[i].y;
v[a[i].y].push_back({a[i].x, i});
}
cin >> s;
bool ans = false;
for (auto x : v) {
vector<pair<int, int>>tmp = x.second;
sort(tmp.begin(),tmp.end());
for (int i = 0; i < tmp.size()-1; i++)
if (s[tmp[i].second] == 'R' && s[tmp[i+1].second] == 'L') ans = true;
}
if (ans) puts("Yes"); else puts("No");
return 0;
}
D - Moves on Binary Tree
题意
有一棵完全二叉树,节点编号从 1 开始,从上到下从左到右对节点依次编号。
从编号为 x 的节点开始走,有一个长度为 n 的序列 s,描述走的每一步。
- s_i 为 U ,表示走到当前节点的父亲节点。
- s_i 为 L ,表示走到当前节点的左儿子节点。
- s_i 为 R ,表示走到当前节点的右儿子节点。
请输出最终走到的节点标号,保证答案在[1,10^{18}]。
数据范围: 1 \leq n \leq 10^6, 1 \leq x \leq 10^{18}
题解
显然,对应的操作序列是 x \rightarrow \{\lfloor \frac{x}{2} \rfloor, 2x,2x+1\}。
这可以类比成:
- x \rightarrow \lfloor \frac{x}{2}\rfloor : 将二进制数 x 的末尾的 0 或者 1 舍去。
- x \rightarrow 2x : 将二进制数 x 的末尾添加一个 0 。
- x \rightarrow 2x + 1 : 将二进制数 x 的末尾添加一个 1 。
这可以使用双端队列进行维护。
时间复杂度 O(n)。
# include <bits/stdc++.h>
# define int long long
using namespace std;
signed main() {
int n, x; cin >> n >> x;
string s; cin >> s;
deque<int>q;
while (x) {
q.push_front(x&1);
x >>= 1;
}
for (char t : s) {
if (t == 'U') q.pop_back();
else if (t == 'L') q.push_back(0);
else if (t == 'R') q.push_back(1);
}
int ans = 0, pw = 0;
while (q.size()) {
ans += q.back() * (1ll << pw);
pw++; q.pop_back();
}
cout<< ans << endl;
return 0;
}
E - Edge Deletion
题意
n 个点 m 条边的带边权连通无向图,第 i (1 \leq i \leq m) 条无向边是(a_i,b_i,c_i),表示 a_i 和 b_i 间有长度为 c_i 的无向边。输出最多可以删除的边数,使得图中任意两点最短距离不变。
数据范围: 2 \leq n \leq 300 , n-1 \leq m \leq \frac{n(n-1)}{2}, 1 \leq c_i \leq 10^9。
题解
首先可以用 \text{floyd} 算法求出两点间的最短距离 d[i][j]。
接下来依次考虑每一条边是否可以被删去,设该边为(u,v,w)。
可以删去的条件是存在一个点 i (1 \leq i \leq n ,i \ne u,i \ne v) 满足 d[u][i] + d[i][v] \leq w。
时间复杂度为 O(n^3 + nm)
C++ 代码示例
# include <bits/stdc++.h>
# define int long long
using namespace std;
const int N = 3e2 + 10;
int a[N*N], b[N*N], c[N*N], d[N][N];
signed main() {
int n, m; cin >> n >> m;
memset(d,0x3f,sizeof(d));
for (int i = 1; i <= n; i++) d[i][i] = 0;
for (int i = 1; i <= m; i++) {
cin >> a[i] >> b[i] >> c[i];
d[a[i]][b[i]] = d[b[i]][a[i]] = c[i];
}
for (int k = 1; k <= n; k++)
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n ; j++)
d[i][j] = min(d[i][j],d[i][k]+d[k][j]);
int ans = 0;
for (int i = 1; i <= m; i++) {
int u = a[i], v = b[i], w = c[i];
for (int j = 1; j <= n; j++)
if (w >= d[u][j] + d[j][v] && u != j && v != j) {
ans++; break;
}
}
cout << ans << endl;
return 0;
}
F - Lottery
题意
有 n 种彩票,每种彩票有一个权值w_i,表示其中奖率为\frac{w_i}{\sum_{i = 1} ^{n}w_i}。
求买 m 张彩票,恰好中了 k 种彩票的奖的概率。
答案对 998244353 取模。
数据范围:1 \leq k \leq 50, 1 \leq m \leq n \leq 50, 0 < w_i \leq 998244353 , 0 \leq \sum_{i = 1} ^{n} w_i \leq 998244353。
题解
假设每种彩票分别买了c_i (1 \leq i \leq n) 张,第 i 种彩票中奖概率为p_i。
中奖的概率可以由超几何分布求出:p = \frac{k!}{\prod_{i=1}^{n} c_i!}\prod_{i = 1} ^ {n} p_i ^ {c_i} = k!\frac{\prod_{i = 1} ^ {n} p_i ^ {c_i}}{\prod_{i=1}^{n} c_i!}。
设 f[i][j][k] 表示前 i 种彩票,一共中奖了j 种,一共买了k张的概率。显然 f[0][0][0] = 1。
转移考虑第 i+1 种彩票买了 x 张,f[i+1][j+(x\ne 0)][k + x] += f[i][j][k] \times \frac{p[i+1]^x}{x!}。
时间复杂度为 O(nmk^2)。
C++ 代码示例
# include <bits/stdc++.h>
# define int long long
using namespace std;
const int N = 55;
const int mo = 998244353;
int f[N][N][N], fac[N], p[N];
int Pow(int x, int n) {
int ans = 1;
while (n) {
if (n & 1) ans = ans * x % mo;
x = x * x % mo;
n >>= 1;
}
return ans % mo;
}
signed main() {
int n, m, l, sum = 0; cin >> n >> m >> l;
for (int i = 1; i <= n; i++) {
cin >> p[i]; sum += p[i];
}
int inv = Pow(sum, mo - 2); f[0][0][0] = 1;
for (int i = 1; i <= n; i++) {
p[i] = p[i] * inv % mo;
}
fac[0] = 1; for (int i = 1; i <= l; i++) fac[i] = fac[i-1] * i % mo;
for (int i = 0; i <= n; i++) for (int j = 0; j <= i; j++)
for (int k = 0; k <= l; k++) for (int c = 0; k + c <= l; c++)
(f[i + 1][j + (c != 0)][k + c] += f[i][j][k] * Pow(p[i + 1], c) % mo * Pow(fac[c], mo - 2) % mo) %= mo;
int ans = f[n][m][l] * fac[l] % mo;
cout << ans << endl;
return 0;
}
G - Sqrt
题意
一开始有长度为 1 的序列,这个序列中只有一个数 x,接下来进行 10^{100} 操作。
每次操作让序列新增一个整数元素,并且这个整数元素的值的取值范围是 1 到当前序列末尾元素的平方根。
求出最终会生成几种不同的序列,答案保证在2^{63}以内。
本题有 T 组数据。
数据范围 1 \leq T \leq 20, 1 \leq x \leq 9\times 10^{18}
题解
f[i] 表示末尾为 i 新增不同序列的数量。 f[i] = \sum_{j = 1} ^{\lfloor \sqrt{i}\rfloor} f[j]。
设 s[i] 为数组 f[i] 的前缀和,则上述转移方程可以写成:f[i] = s[\lfloor \sqrt{i}\rfloor]。
初始值为 f[1] = s[1] = 1。而s[n] = \sum_{i = 1} ^ {n} f[i] = \sum_{i = 1} ^ {n} s[\lfloor \sqrt{i}\rfloor]
因此f[x] = \sum_{i = 1} ^{\lfloor \sqrt{x}\rfloor} f[i] = s[\lfloor \sqrt{x}\rfloor] = \sum_{i = 1} ^ {\lfloor \sqrt{x}\rfloor} s[\lfloor \sqrt{i}\rfloor]。
只需要进行 O(x^{1/4})的预处理即可。
注意大整数开根号取整直接用sqrt(),可能出现精度问题。
一种可行的办法是用二分代替。
时间复杂度为 O(Tx^{1/4})。
C++ 代码示例
# include <bits/stdc++.h>
# define int unsigned long long
using namespace std;
const int N = 1e6 + 10;
int f[N], s[N];
int getsqr(int x) {
int l = 0, r = 3e9 + 1, ans;
while (l <= r) {
int mid = (l + r) >> 1;
if (mid * mid <= x) ans = mid, l = mid + 1;
else r = mid - 1;
}
return ans;
}
signed main() {
int T; cin >> T;
while (T--) {
int n; cin >> n;
int h = getsqr(n), l = getsqr(h), ans = 0;
f[1] = s[1] = 1;
for (int i = 2; i <= l; i++) {
f[i] = s[getsqr(i)];
s[i] = s[i - 1] + f[i];
}
for (int i = 1; i <= l; i ++) {
int l = i * i, r = min((i + 1) * (i + 1) - 1, h);
ans += (r - l + 1) * s[i];
}
cout << ans <<endl;
}
return 0;
}