ABC368 参加記
日立ヴァンタラプログラミングコンテスト2024(AtCoder Beginner Contest 368)に参加しました。
A - Cut
後ろ $K$ 枚と前 $N-K$ 枚を順に出力すればよい。
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int N, K; cin >> N >> K;
vector<int> A(N); v_cin(A);
vector<int> ans;
rrep(i, N - K, N){
ans.push_back(A[i]);
}
rep(i, N - K){
ans.push_back(A[i]);
}
v_cout(ans);
}
B - Decrease 2 max elements
問題文の通りに、
- $A$ を降順ソート
- $A_0 \leftarrow A_0-1,~A_1 \leftarrow A_1-1$
を $A$ の正の要素の個数が $1$ 以下になるまで繰り返すという操作を行えばよい。 計算量は $O(N\log (N) \max(A))$ で十分に間に合う。
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int N; cin >> N;
vector<int> A(N); v_cin(A);
int ans = 0;
while(true){
sort(rall(A));
ans ++;
A[0] --; A[1] --;
int cnt = 0;
rep(i, N){
if(A[i] > 0) cnt ++;
}
if(cnt <= 1) break;
}
cout << ans << '\n';
}
C - Triple Attack
愚直にシミュレーションすると $O(N\max (A))$ となって間に合わない。$T$ がどんな場合でも $3$ 回の行動で敵の体力を $5$ 減らすことができるので、任意の $1\leq i\leq N$ に対して $T \leftarrow T + \lfloor \frac{H_i}{5} \rfloor$ としたあとに、$H_i$ を $5$ で割った余りで場合分けする。
公式解説は、割った後は愚直にシミュレーションしていた。
計算量は $O(N)$ になる。
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int N; cin >> N;
vector<int> H(N); v_cin(H);
ll T = 1;
rep(i, N){
int q = H[i] / 5;
int r = H[i] % 5;
T += 3 * q;
if(T % 3 == 0){
if(r >= 1 and r <= 3) T ++;
if(r == 4) T += 2;
}else if(T % 3 == 1){
if(r == 1) T ++;
if(r == 2) T += 2;
if(r >= 3 and r <= 4) T += 3;
}else{
if(r == 1) T ++;
if(r >= 2 and r <= 4) T += 2;
}
}
cout << T - 1 << "\n";
}
D - Minimum Steiner Tree
$V$ に含まれる点の $1$ つを根として考えれば、求める部分木は根を必ず含む。$dp_i$ を $i$ を根とする部分木の頂点のうち、求める部分木に含まれる頂点の数として定める。このとき、親のノードを $p$、子のノードの集合を $C$ とすると、$p \in V \text{ または } \sum_{c\in C}dp_c \geq 1$ のとき
\[dp_p = \sum_{c\in C}dp_c + 1\]それ以外のとき $dp_p = 0$ となり解ける。わざわざDPを使う必要はないかもしれない。
int N, K;
vector<vector<int>> G;
vector<int> dp;
vector<bool> V;
vector<bool> visited;
void dfs(int pos){
visited[pos] = true;
int cnt = 0;
rep(i, G[pos].size()){
int nxt = G[pos][i];
if(visited[nxt]) continue;
dfs(nxt);
cnt += dp[nxt];
}
if(V[pos] or cnt > 0) cnt ++;
dp[pos] = cnt;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> N >> K;
G.resize(N);
rep(i, N - 1){
int a, b; cin >> a >> b; a --; b --;
G[a].push_back(b);
G[b].push_back(a);
}
V.assign(N, false);
rep(i, K){
int v; cin >> v; v --;
V[v] = true;
}
visited.assign(N, false);
dp.assign(N, 0);
rep(i, N){
if(V[i]){
dfs(i);
cout << dp[i] << "\n";
break;
}
}
}
F - Dividing Game
長さ $N$ の数列 $B$ を
\[B_i = A_i\text{ の素因数の個数 (重複あり)}\]とすると、$B$ の Nim の問題に帰着できる。Nim の問題を解いたことがなかったので、鉄則本の該当ページを丸写しした。
vector<pair<long long, long long>> prime_factorize(long long N){
vector<pair<long long, long long>> prime;
for(long long i = 2; i * i <= N; i++){
if(N % i != 0) continue;
long long e = 0;
while(N % i == 0){
e++;
N /= i;
}
prime.push_back({i, e});
}
if(N != 1) prime.push_back({N, 1});
return prime;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int N; cin >> N;
vector<int> A(N); v_cin(A);
vector<int> ps(N, 0);
rep(i, N){
auto p = prime_factorize(A[i]);
for(auto [x, y] : p){
ps[i] += y;
}
}
int sum = ps[0];
rrep(i, 1, N){
sum = sum ^ ps[i];
}
if(sum > 0) cout << "Anna\n";
else cout << "Bruno\n";
}
コンテスト結果
- コンテスト成績表
- ABCDF5完 80分
- パフォーマンス: 1373
- レーティング: 1105 → 1135
コメント
来週までにNim と Grundy 数の話を勉強したい。