AtCoder Beginner Contest 368 题解&&思路(A-D,F)
A - Cut
题目描述
有 N N N 个数在一个桶里面,从上往下第 i i i 个数是 A i A_i Ai,从桶下面取出 K K K 个数,保持原顺序放在桶的上面,从上到下打印写在卡片上的整数。
思路
时间复杂度 O ( N ) . \mathcal{O}(N). O(N).
本质上就是先输出后 K K K 个数然后再从前面输出。
#include <iostream>
#include <cstdio>
#include <stdlib.h>
#define N 105
using namespace std;
int a[N];
signed main(){
int n,k;
cin >> n >> k;
for (int i = 1;i <= n;i ++) cin >> a[i];
for (int i = n - k + 1;i <= n;i ++) cout << a[i] << ' ';
for (int i = 1;i < n - k + 1;i ++) cout << a[i] << ' ';
return 0;
}
B - Decrease 2 max elements
题目描述
问题陈述
给你一个由 N N N 个正整数 A = ( A 1 , A 2 , … , A N ) A = (A_1, A_2, \dots ,A_N) A=(A1,A2,…,AN) 组成的序列。高桥重复下面的操作,直到 A A A 包含一个或更少的正整数元素:
- 按降序排列 A A A 。然后将 A 1 A_1 A1 和 A 2 A_2 A2 减少 1 1 1 。
求他执行此操作的次数。
思路
直接模拟即可。
用数组或者 m a p map map 存一下大于 0 0 0 的有多少个就行,每次操作后更新。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <stdlib.h>
#include <map>
#define N 105
using namespace std;
int n,a[N],ans;
map<bool,int> mp;
signed main(){
cin >> n;
for (int i = 1;i <= n;i ++) cin >> a[i];
for (int i = 1;i <= n;i ++) {
if (a[i] > 0) mp[1] ++;
else mp[0] ++;
}
while(mp[1] > 1) {
stable_sort(a + 1,a + 1 + n,[](int x,int y) {
return x > y;
});
a[1] --,a[2] --;
if (a[1] == 0) mp[1]--,mp[0] ++;
if (a[2] == 0) mp[1]--,mp[0] ++;
ans ++;
}
cout << ans;
return 0;
}
C - Triple Attack
题目描述
问题陈述
你正在玩一个游戏。
有 N N N 个敌人排成一排,最前面的 i i i 个敌人的生命值是 H i H_i Hi 。
你将使用初始化为 0 0 0 的变量 T T T 重复以下操作,直到所有敌人的生命值都变为 0 0 0 或更少。
- 将 T T T 增加 1 1 1 。然后,攻击最前方生命值为 1 1 1 或更高的敌人。如果 T T T 是 3 3 3 的倍数,敌人的生命值会减少 3 3 3 ;否则,生命值会减少 1 1 1 。
当所有敌人的生命值变为 0 0 0 或更少时,求 T T T 的值。
思路
赛时有细节问题,没打出来,呜呜呜呜
我们可以发现一个循环节:
T
=
3
,
4
,
5
T=3,4,5
T=3,4,5
这里攻击对方是
5
5
5 的健康值,而只用迭代
3
3
3 次
T
.
T.
T.
对于没有到 T % 3 = 0 T\%3=0 T%3=0 的,暴力枚举即可(我赛时就是因为没有这样,想用 O ( 1 ) O(1) O(1) 解决,导致有一大堆细节问题……)。
其他的就不必多说了。
#include <iostream>
#include <cstdio>
#include <stdlib.h>
#include <cstring>
#include <algorithm>
#define N 200005
#define int long long
using namespace std;
int n,a[N],T = 0;
signed main(){
cin >> n;
for (int i = 1;i <= n;i ++) cin >> a[i];
for (int i = 1;i <= n;i ++) {
while(a[i] > 0 && T % 3) {
T ++;
if (T % 3) a[i]--;
else a[i] -= 3;
}
if (a[i] > 0) {
int m = a[i] / 5;
T += m * 3;
a[i] %= 5;
if (a[i] >= 1) T ++;
if (a[i] >= 2) T ++;
if (a[i] >= 3) T ++;
}
}
cout << T;
return 0;
}
D - Minimum Steiner Tree
题目描述
问题陈述
给你一棵树,树上有 N N N 个顶点,编号为 1 1 1 至 N N N 。第 i i i 条边连接顶点 A i A_i Ai 和 B i B_i Bi 。
考虑从这个图中删除一些边和顶点(可能为零)后可以得到一棵树。求这样一棵树中包含所有 K K K 指定顶点 V 1 , … , V K V_1,\ldots,V_K V1,…,VK 的顶点的最小数目。
思路
注意到题目给的是一棵树,也就是说从
u
u
u 到
v
v
v 有且仅有一条路径,难道这里我们要枚举
V
i
,
V
j
V_i,V_j
Vi,Vj ?其实不用,我们可以从
V
1
V_1
V1 为根开始 dfs
图的遍历,碰到节点
x
∈
V
x\in V
x∈V 的,就更新这条路径上的点(表示算作答案),但一定要先遍历再更新(有可能当前节点
u
∈
V
u\in V
u∈V 而
x
∈
V
x\in V
x∈V 且
x
x
x 是
u
u
u 子树下的一个点,这样就会顺便把
V
1
→
u
V_1\rightarrow u
V1→u 的路径也给更新了)。
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <stdlib.h>
#include <vector>
#include <set>
#define N 200005
using namespace std;
int n,v[N],k;
vector<int> g[N];
bool bj[N];
bool vis[N];
int stk[N],tp;
void dfs(int cur,int fa) {
tp ++,stk[tp] = cur;
for (auto i : g[cur])
if (i != fa){
dfs(i,cur);
if (bj[i] && !vis[i]) {
for (int j = 1;j <= tp;j ++) vis[stk[j]] = 1;
vis[i] = 1;
}
}
tp --;
}
signed main(){
cin >> n >> k;
for (int i = 1;i < n;i ++) {
int u,v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
for (int i = 1;i <= k;i ++) cin >> v[i],bj[v[i]] = 1;
dfs(v[1],0);
vis[v[1]] = 1;
int ans = 0;
for (int i = 1;i <= n;i ++) ans += vis[i];
cout << ans;
return 0;
}
E - Train Delay
题目描述
问题陈述
在 Atcoder 国家,有 N N N 座城市,编号为 1 1 1 至 N N N ,以及 M M M 列火车,编号为 1 1 1 至 M M M 。 i i i 次列车在 S i S_i Si 点从城市 A i A_i Ai 出发,在 T i T_i Ti 点到达城市 B i B_i Bi 。
给定一个正整数 X 1 X_1 X1 ,求一个非负整数 X 2 , … , X M X_2,\ldots,X_M X2,…,XM 的最小值 X 2 + … + X M X_2+\ldots+X_M X2+…+XM ,以满足下列条件。
- 条件对于所有满足
1
≤
i
,
j
≤
M
1 \leq i,j \leq M
1≤i,j≤M 的一对
(
i
,
j
)
(i,j)
(i,j) ,如果
B
i
=
A
j
B_i=A_j
Bi=Aj 和
T
i
≤
S
j
T_i \leq S_j
Ti≤Sj ,那么
T
i
+
X
i
≤
S
j
+
X
j
T_i+X_i \leq S_j+X_j
Ti+Xi≤Sj+Xj 。
- 换句话说,对于任何一对原本可以换乘的列车,即使将每趟列车 i i i 的出发和到达时间延迟 X i X_i Xi ,仍然可以换乘。
可以证明,这种将 X 2 , … , X M X_2,\ldots,X_M X2,…,XM 设置为 X 2 + … + X M X_2+\ldots+X_M X2+…+XM 的最小值的方法是唯一的。
思路
赛场上没有想出来就先做 T 6 T_6 T6 了,后面更。
F - Dividing Game
题目描述
问题陈述
给你一个由 N N N 个正整数 A = ( A 1 , A 2 , … , A N ) A = (A_1, A_2, \dots ,A_N) A=(A1,A2,…,AN) 组成的序列,其中每个元素至少是 2 2 2 。安娜和布鲁诺用这些整数玩一个游戏。他们轮流执行以下操作,安娜先执行。
- 自由选择一个整数 i ( 1 ≤ i ≤ N ) i \ (1 \leq i \leq N) i (1≤i≤N) 。然后,自由选择 A i A_i Ai 的一个不是 A i A_i Ai 本身的正整除 x x x ,并将 A i A_i Ai 替换为 x x x 。
不能进行操作的一方输,另一方赢。假设两位棋手都以最佳的方式下棋,那么谁会赢呢?
思路
这是道博弈论的题目,不难想出来这题应该和因数有关,考虑时间复杂度 O ( N N ) \mathcal{O}(N\sqrt N) O(NN) 的算法。
看到此题我先求出其因数的个数(除了它本身),然后,观察到现在是不是跟之前做过的题目一样,没错是它!
——Nim 游戏
但交上去,挂了……
现在我考虑这条关于 A i A_i Ai 的因子链,发现跟它的最长链有关,不过也很好理解,拿最长能够使先手必胜的状态更大,于是就 A A A 了。
#include <iostream>
#include <cstdio>
#include <algorithm>
#define N 100005
using namespace std;
int n,a[N],cnt;
signed main(){
cin >> n;
for (int i = 1;i <= n;i ++) {
cin >> a[i];
int p = 0;
for (int j = 2;j * j <= a[i];j ++)
while(a[i] % j == 0) a[i] /= j,p ++;
if (a[i] > 1) p ++;
cnt ^= p;
// cout << p << endl;
}
if (cnt == 0) return cout << "Bruno",0;
cout << "Anna";
return 0;
}