11월 21일 있었던 코드포스 라운드 685의 풀이이다.
주어진 수 \(N\)을 두 가지 연산을 이용해 \(1\)로 만드는데, 필요한 연산 횟수의 최솟값을 구하는 문제이다.
두 가지 연산은 각각 수를 자신의 진약수로 나누거나, \(1\)을 빼는 연산이다.
결정적인 관찰은 \(N\)이 짝수이면 1번의 연산만으로 무조건 \(N\)을 \(2\)를 만들 수 있는 것이다. 이것을 늦게 발견해서 이 문제를 푸는 데 25분이나 걸렸다.
시간 복잡도는 테스트 케이스 당 \(O(1)\)이다.
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(0); cin.tie(0);
int T; cin >> T; while (T--) {
long long N; cin >> N;
int cnt = 0;
while (1) {
if (N == 2) {
cout << cnt + 1 << '\n';
break;
}
if (N == 1) {
cout << cnt << '\n';
break;
}
if (N % 2 == 1) {
N--; cnt++;
continue;
}
N = 2; cnt++;
continue;
}
}
}
주어진 문자열에 대해 쿼리를 처리하는 문제이다. 쿼리로 주어진 구간을 \([L, R]\)이라고 하면, \([0,L-1]\) 중에서 하나 이상의 문자가 \(L\)과 같거나, \([R+1, N-1]\)중에서 하나 이상의 문자가 \(R\)과 같으면 YES이다. 이것을 Naive하게 구현하면 쿼리 당 \(O(N)\)에 처리가 가능하고, 제한이 작기 때문에 이 방법도 Accepted를 받을 수 있다.
더 빠르게 하려면, Prefix Sum을 이용해 0과 1의 개수를 저장해놓고, 쿼리 당 \(O(1)\)으로 처리하면 된다. 대략적인 아이디어는, 먼저 \(psum_0[i] = i\)번째 index까지의 \(0\)의 개수, \(psum_1[i] = i\)번째 index까지의 \(1\)의 개수로 놓는다. 쿼리가 들어올 때 \(psum_i[L-1]\)과 \(psum_i[0]\)을 비교하고, \(psum_j[R]\)과 \(psum_j[N]\)을 비교해서 하나라도 다르다면 YES임을 알 수 있다\((\)단, \(i = str[L], j = str[R])\).
실제 대회에서는 Naive 풀이가 통과되는 것을 캐치하지 못하고 Prefix Sum으로 풀었다.
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(0); cin.tie(0);
int T; cin >> T; while (T--) {
int N, Q; cin >> N >> Q;
string str; cin >> str;
vector<int> p1(N + 1), p0(N + 1);
p1[0] = p0[0] = 0;
for (int i = 0; i < N; i++) {
p1[i + 1] = p1[i] + (str[i] == '1');
p0[i + 1] = p0[i] + (str[i] == '0');
}
while (Q--) {
bool good = 0;
int L, R; cin >> L >> R;
if (str[L - 1] == '0') {
if (p0[L - 1] != 0)good = 1;
}
if (str[L - 1] == '1') {
if (p1[L - 1] != 0)good = 1;
}
if (str[R - 1] == '0') {
if (p0[N] != p0[R])good = 1;
}
if (str[R - 1] == '1') {
if (p1[N] != p1[R])good = 1;
}
cout << (good ? "YES\n" : "NO\n");
}
}
}
문자열을 적당히 변환시켜 다른 문자열로 만들 수 있는지 물어보는 문제이다. 가능한 연산은 연속된 두 개의 문자의 순서를 바꾸는 것과, \(k\)개의 연속된 문자열의 알파벳 순서를 \(1\) 올리는 것이다. 단, 두 번째 연산에서 모든 \(k\)개의 문자열은 같은 알파벳이어야 한다.
먼저 관찰을 통해 첫 번째 연산을 무한정 사용할 수 있으므로 문자열의 순서는 중요하지 않다는 것을 알 수 있다. 그러면 각 문자가 몇 개인지가 중요한데, 두 번째 연산으로는 문자열의 알파벳 순서를 올릴 수만 있고, 내릴 수는 없다. 그렇다면, 할 수 있는 최선의 수는 \(a\)부터 시작해서 해당 문자가 목표 문자열에 사용되는 만큼 개수를 빼준 뒤, 나머지를 모두 다음 알파벳으로 올리는 것이다. 이 때, 빼주고 나서의 값이 \(k\)로 나누어떨어져야 가능한 경우의 수가 된다. 만약 \(a\)부터 \(y\)까지 이 조건을 만족하고, \(z\)에서 원래 문자열이랑 목표 문자열이랑 개수가 같으면, 답이 YES이다.
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(0); cin.tie(0);
int T; cin >> T; while (T--) {
int N, K; cin >> N >> K;
string s1, s2;
cin >> s1 >> s2;
vector<int> m1(26, 0), m2(26, 0);
for (int i = 0; i < N; i++) {
m1[s1[i] - 'a']++;
m2[s2[i] - 'a']++;
}
bool pos = 1;
for (int i = 0; i < 26; i++) {
int left = m1[i] - m2[i];
if (left < 0) {
pos = 0;
break;
}
if (left%K != 0) {
pos = 0;
break;
}
m1[i + 1] += left;
if (i == 25) {
if (left != 0)pos = 0;
}
}
cout << (pos ? "Yes\n" : "No\n");
}
}
오랜만에 재미있었던 셋이었다.
'알고리즘' 카테고리의 다른 글
Codeforces Round #687 (Div.2) (0) | 2020.11.30 |
---|---|
Codeforces Round #686 (Div.3) (0) | 2020.11.25 |
Educational Codeforces Round 98 (Div.2) (0) | 2020.11.21 |
Codeforces Round #684 (Div.2) (0) | 2020.11.18 |
Codeforces Round #663 (Div.2) (0) | 2020.08.10 |