
Codeforces Round #737 (Div. 2) A부터 C까지 업솔빙
falconlee236
·2021. 12. 30. 23:21
Codeforces Round #737 (Div. 2) A부터 C까지 업솔빙
사실 코드포스에서 A번 문제가 5분 넘을때 까지 안풀리면 그 대회는 참가하지 않는 것이 RATING에 이로울 정도로 A번 문제를 쉽게 풀 수 있는지 없는지가 중요하다. 이번 대회는 A번 문제를 생각보다 빠르게 풀어서 기분이 좋았다. 하지만 바로 B번부터 막혀서 기분이 안좋았지만 나머지 B, C번 문제가 좋아서 업솔빙을 한다. 이번 대회에서 배운 것은
- 경로 압축 방법과 value-index pair 배열의 활용
- 이항계수의 성질 복습 및 dp
- 제발 정렬하는 것도 생각해보자.
A. Ezzat and Two Subsequences (*800)
접기/펼치기
문제 설명
배열 1개가 주어지고 이 배열을 두 subsequences
이때 subsequence 는 한 배열에서 무작위 원소를 제거해서 얻을 수 있는 수열이다. 즉 배열의 원소는 연속할 필요가 없다.
문제 해설
이 문제는 두가지 방법으로 접근할 수 있다.
- naive하게 접근하는 방법: 작은 것은 작은 것끼리, 큰 것은 큰 것끼리 모아서 평균을 구하면 전체 평균이 크기 때문에 배열을 정렬 한 다음에 처음부터 끝까지 배열을 두개로 쪼개서 얻은 모든 값 중에서 최대값을 구한다. 즉 총 배열의 원소가 10개라면
이런식으로 배열의 원소를 index 0부터 차례대로 평균을 구한다. - 가장 큰 원소를 a라고 하고, 나머지 배열을 b라고 한다. 1번 접근법과 비슷하지만 좀 더 직관적인 방법이다. 가장 큰 원소를 나누지 않아서 최대한 큰 값을 보존하고 나머지 값들을 평균내서 구하는 방법이다. 이 사실을 문제에 나오는 example에서 조금만 해본다면 답이 이거일까? 라고 생각이 들면서 바로 시도해볼 수 있다. 사실 그냥 때려 맞추는 문제가 codeforce A번에 자주 나온다. 이거 증명하는 과정은 솔직히 이해 안된다.
정답 코드
#include <iostream>
#include <algorithm>
using namespace std;
int main() {
ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int t; cin >> t;
while(t--){
int n; cin >> n;
long long sum = 0, mx = -1e10;
for(int i = 0; i < n; i++){
long long num; cin >> num;
sum += num;
mx = max(mx, num);
}
cout.precision(10);
cout << fixed << 1.0 * (sum - mx) / (n - 1) + mx * 1.0 << "\n";
}
return 0;
}
B. Moamen and k-subarrays (*1100)
접기/펼치기
문제 설명
서로 다른
- 배열을
개 빈 원소가 없는 subarray 로 분할한다. - 이 subarray 를 임의 순서대로 재배열 한다.
- 재배열한 subarray 를 합친다.
subarray 와 subsequence 의 차이점은 subsequecne는 주어진 배열에서 임의의 원소를 선택해서 만들었다면 subarray는 반드시 주어진 배열에 있는 원소를 연속해서 선택해야 한다는 점이다. 주어진 배열의 원소를 연속해서 선택했는지 여부가 subarray와 subsequence의 차이점이다.
문제 해설
난이도 800, 900, 1000, 1100, 1200문제를 안정적으로 실수없이 반드시 푸는게 일단 내 목표이다. 이 문제는 내 목표의 장애물중 하나.
문제를 풀면서 느낀점은 자꾸 문제에서 요구하는 연산을 무조건 순서대로 처리하려고 하는 것이다. 이 연산도 중요하지만 결국은 이 문제에서 중요한 점은 배열을 나누었을 때 오름차순으로 만들 수 있는가? 인데 나는 문제를 풀때마다
이 배열이 오름차순으로 재배열 할 수 있게 하려면 기본적으로 모든 수가 오름차순으로 정렬되어 있다면 자동으로 가능할 것이다. 이 배열이 내림차순으로 되어있는 구간이 있다면 그 구간은 반드시 둘로 쪼개야 해야 재배열을 할 때 오름차순으로 만들 수 있기 때문이다.
그렇다면 무작정 내림차순인 구간만 찾아서 개수를 세면 답이 나오는가? 내가 여기까지 밖에 생각을 못했다. 연속하는 두 원소가 오름차순이어도 다른 index의 원소에서 그 구간에 사이에 있는 원소가 존재하다면 그 구간은 또 둘로 나누어야 한다. 왜냐하면 사이에 있는 원소가 다른 곳에 있기 때문에 그 원소가 그 구간에 가운데에 반드시 있어야 전체 배열이 오름차순이 될 수 있기 때문이다.
이런 문제를 풀기 위해서 원소의 값과 index를 동시에 가지고 있는 배열을 만든 다음 그 배열을 값을 기준으로 정렬한다. 그렇다면 오름차순으로 이미 되어 있는 구간이라면 정렬된 배열을 처음부터 확인할 때 index가 오름차순으로 되어있을 것이다. 이것만 확인하면 안된다. 그 구간 사이에 원소가 없으려면 index 배열을
정답 코드
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int main() {
ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int t; cin >> t;
while(t--){
int n, k; cin >> n >> k;
vector<pair<int, int>> v;
for(int i = 0; i < n; i++){
int num; cin >> num;
v.push_back({num, i});
}
sort(v.begin(), v.end());
int cnt = 0;
for(int i = 0; i < n - 1; i++){
if(v[i].second + 1 != v[i + 1].second) cnt++;
}
cout << (cnt < k ? "Yes" : "No") << "\n";
}
return 0;
}
C. Moamen and XOR (*1700)
접기/펼치기
문제 설명
각 원소의 값이 모두
다음 조건을 만족하는 배열의 개수를 구하시오.
& & & &
결과 값이 매우 크므로 값을
문제 해설
보자마자 때려친 문제이지만 계속 업솔빙 하면 풀 수 있을 것이라 믿으며 계속 공부한다.
이 문제는 dp와 비트마스크를 결합한 문제이다. 일단 문제에서
배열
& 연산자는 모든 비트의 값이
이 짝수일 경우.만약 첫번째 열의 모든 값이 1이면 & 연산의 값은 1이다. 하지만
연산은 1이 짝수개 있으므로 값이 0이다. 즉 무조건 조건을 만족하기 때문에 뒤에 있는 비트는 볼 필요가 없다. 총 배열의 열은 개 이므로 개 비트가 가질 수 있는 모든 경우의 수는 개이고, 이 값을 빠르게 구하기 위해서 빠른 거듭제곱 알고리즘을 미리 구현해 둔다.만약 첫번째 열의 모든 값이 1이 아니라면 & 연산의 값은 0이므로 다음 비트를 비교할 수 있는 기회를 얻기 위해서는
연산의 값이 0이 되어야 한다. 이때 이 짝수개인 상태에서 짝수개의 원소의 비트가 1이 되어야 하기 때문에 그 경우의 수는 이다. 이때 이항계수의 총 합은 성질에 의해 개 이고, 경우는 한 열의 모든 비트가 인 경우이므로 이미 위에서 계산했다. 따라서 1을 빼준다. 첫번째 열에서 얻을 수 있는 경우의 수는 개 이고 나머지 열 의 가장 맨 앞에 있는 열을 토대로 구한 경우의 수와 곱하면 최종 답이다. 여기서 일 때 구해야 하는 경우의 수를 빠르게 구하기 위해서 우리는 다이나믹 프로그래밍 기법을 사용한다.우리는
를 다음과 같이 정의한다. LSB부터 번째 열까지 확인했을 때, 얻을 수 있는 경우의 수. 이때 이다. 왜냐하면 번째 열은 각 원소의 값이 보다 작은 경우를 의미하고, 그 경우에는 모든 원소가 0인 경우 한가지 밖에 없기 때문이다.앞서 정의한 dp를 사용해서
이 짝수인 경우 점화식을 다시 구하면 다음과 같다.
이 홀수인 경우.만약 첫번째 열의 모든 값이 1이라면 & 연산의 값은 1이지만
연산의 값은 1이 홀수개 있으므로 값이 이다. 즉 다음 개 열의 경우의 수를 더해야 한다.만약 첫번째 열의 모든 값이 1이 아니라면
의 값이 0이 되어야 하고, 은 홀수이므로 짝수개를 선택하려면 값을 구해야하고, 이 값도 마찬가지로 이항계수의 성질에 따라 이다. 그리고 다음 열의 경우의 수를 곱하고 마지막에 더하면 된다.앞서 정의한 dp를 사용해서
이 홀수인 경우 점화식을 다시 구하면 다음과 같다.
정답 코드
#include <iostream>
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
ll poww(ll a, ll b){
ll res = 1;
while(b){
if(b & 1) res = (res * a) % mod;
a = (a * a) % mod;
b >>= 1;
}
return res;
}
ll dp[200001];
int main() {
ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int t; cin >> t;
dp[0] = 1;
while(t--){
ll n, k; cin >> n >> k;
ll base = poww(2LL, n - 1);
for(int i = 1; i <= k; i++){
if(n & 1) dp[i] = (dp[i - 1] * (base + 1)) % mod;
else dp[i] = (poww(2LL, n * (i - 1)) + (base - 1) * dp[i - 1]) % mod;
}
cout << dp[k] << "\n";
}
return 0;
}
'알고리즘 > codeforces' 카테고리의 다른 글
Codeforces Round #753 (Div. 3) A부터 D까지 업솔빙 (0) | 2022.01.07 |
---|---|
Codeforces Round #752 (Div. 2) A부터 D까지 업솔빙 (0) | 2022.01.02 |
Educational Codeforces Round 112-C. Coin Rows (0) | 2021.12.25 |
Educational Codeforces Round 112-B. Two Tables (0) | 2021.12.25 |
Educational Codeforces Round 112-A. PizzaForces (0) | 2021.12.25 |