codeforce div 2
COFO::Round 610
cofo round
COFO::Round #610 ( div 2 )
- Link : COFO::round 610 ( div 2 )
- solved :
- A : ( 00:11 )
- B1 : ( 00:35 )
- B2 : ( 01:45 )
- rank : 2404
- score : 1288
- 아직 실력이 많이 부족해서인지, C번은 참 쉽지 않은 문제인 것 같습니다.
- 아이디어는 떠올랐지만, 인덱스를 조작하는 문제는 확실한 그림과 설명과 정의를 내리고 시작하지 않는 한, 끝을 볼 수 없음을 또한번 느꼈습니다.
Problem A : Temporarily unavailable
- level : 900
- tag : implementation, math
- time : 00: 11
Point
- a, b, c, r 이 주어집니다.
- 범위 [a, b]인 직선이 존재하고, 중점이 c이고 반지름이 r인 원이 존재합니다.
- 이때, 범위 [a,b]인 직선 중 원과 겹치는 부분을 제외한 부분의 길이를 출력합니다.
Design(x)
- 범위 체크이기에 음수 처리가 까다로워질 수 있으므로 모두 양수로 만들어 주었습니다.
- 이후 3가지 경우로 나누어 풀이하였습니다.
- 원의 중심이 a보다 작은 경우
- 원의 중심이 b보다 큰 경우
- 그 외의 경우
Big O(time)
- O(1)
Code(x)
#include<bits/stdc++.h>
#define endl '\n'
#define all(v) (v).begin(), (v).end()
#define pb push_back
#define rep(i,a,b) for(int i=a;i<b;i++)
#define r_rep(i,a,b) for(int i=a;i>b;i--)
typedef long long ll;
const ll SHIFTs = 10000 * 10000;
using namespace std;
void process(){
ll a, b, c, r; cin >> a >> b >> c >> r;
if(a > b) swap(a, b);
a += SHIFTs, b += SHIFTs, c += SHIFTs;
ll len = b - a;
if(c <= a){
ll gap = (c + r) - a;
if(gap > 0) len = max((ll)0, len-gap);
}else if( c >= b){
ll gap = b - (c - r);
if(gap > 0) len = max((ll)0,len-gap);
}else{
ll le = c-r, ri = c+r;
len = max((ll)0, le-a) + max((ll)0, b-ri);
}
cout << len << endl;
}
int main(){
ios_base::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int tc; cin >> tc;
rep(i, 1, tc+1){
process();
}
return 0;
}
Problem B1 : K for the Price of One(Easy version)
- level : 1400
- tag : dp, greedy, sortings
- time : 00:35
Point
- n, p, k와 배열 a가 주어집니다.
- p라는 돈으로 가장 많은 갯수의 원소를 사려고 합니다.
- 규칙은 다음과 같습니다.
- 무언가를 살때 경우의 수는 다음 두 가지 입니다.
- 원소 하나만 해당 원소의 가격만큼 구매하거나
- k개의 원소를 묶음으로 구매하고, 이때 가격은 묶음에 포함된 가장 비싼 원소가 됩니다.
Design(x)
- 해당 문제에서는 k가 2로 고정되어 있습니다.
- 문제를 접근하기 위해 여러 가지를 생각해보았습니다.
- 정렬은 일단 해야할 것 같고,
- 어떻게 묶어야 이상적일지,,
- 결론은 이렇습니다.
- 묶음은 순서대로 묶으면 됩니다.
- 즉, 1, 2, 3, 4, 5, 6 이 있을때
- 2가지 경우의 수가 나올 수 있습니다.
- {1}, {2, 3}, {4, 5}
- {1, 2}, {3, 4}, {5}
- 즉, 돈이 모자라는 경우가 무조건 존재할테고 어떻게 greedy해야 가장 많은 것을 구매할 수 있는지 고려해보았을때,
- 앞에 있는 것들이 가격이 싸므로 앞에서부터 묶는 것이 맞습니다.
- 다음과 같은 예를 통해 구체적으로 어떻게 묶어야 할지 설명하겠습니다.
- 1, 2, 3이 있고 4원이 있다고 합시다.
- 순서대로 {1, 2}를 묶으면 3을 살 수 없습니다.
- 하지만, {1}, {2, 3}이렇게 하면 3을 살 수 있게 됩니다.
- 즉, k의 값에 의해 맨 앞 그룹의 원소의 갯수가 [1, k-1]까지 달라질 수 있습니다.
- 현재는 k가 2이므로 총 2가지 경우를 손으로 따로 구해주었습니다.
Big O(time)
- O(N)
Code(x)
#include<bits/stdc++.h>
#define endl '\n'
#define all(v) (v).begin(), (v).end()
#define pb push_back
#define rep(i,a,b) for(int i=a;i<b;i++)
#define r_rep(i,a,b) for(int i=a;i>b;i--)
typedef long long ll;
const int MAXN = 2 * 100 * 1000 + 1;
using namespace std;
int n, coin, k, ans;
int a[MAXN];
void solve(int *x, int sz, int money){
int ret = (sz == n ? 0 : 1);
rep(i, 0, sz-1){
if(x[i+1] <= money){
money -= x[i+1];
ret += 2;
i += 1;
}else if(x[i] <= money){
money -= x[i];
ret += 1;
}else break;
}
ans = max(ans, ret);
}
void process(){
ans = 0;
cin >> n >> coin >> k;
rep(i, 0, n) cin >> a[i];
sort(a, a+n);
solve(a, n, coin);
if(coin >= a[0]) solve(a+1, n-1, coin-a[0]);
cout << ans << endl;
}
int main(){
ios_base::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int tc; cin >> tc;
rep(i, 1, tc+1){
process();
}
return 0;
}
Problem B2 : K for the Price of One ( Hard version )
- level : 1600
- tag : dp, greedy, sortings
- time : 01:45
- 인덱스 조작이 쉽지 않은 구현문제였고,
- TLE 해결법이 마지막에 생각나서 푼 문제였습니다.
Point
- B1과 같지만 k가 변수입니다.
Design(x)
- B1과 같은 방법으로 풀면, O(N * K)가 되어서 TLE를 받게됩니다.
- 따라서, 어떻게 하면 시간을 줄일 수 있을지 고민하였습니다.
- 결론은 이분 탐색이었습니다.
- 우선 시작점을 정합니다.
- 즉, 앞에서부터 몇개까지를 각 원소당 1개씩 개별구매할지 먼저 정해줍니다.
- 그럼 개별 구매는 psum으로 해결할 수 있습니다.
- 이후 남은 돈을 가지고 k개씩 선택하는 solve함수에 보내줍니다.
- 여기서는 행동이 똑같습니다.
- 계속 k개씩 더하면 됩니다.
- 이후, 더 이상 k개씩 묶을 수 없는 경우를 처리해주어야 TLE를 면할 수 있습니다.
- 이 과정에서도 자세히 보면, 어차피 원소의 갯수가 k개가 안됩니다.
- 즉, 개별구매해야 합니다.
- 따라서, for문을 돌리며 앞에서부터 체크하기보단 psum을 이용하여 이분탐색으로 가능한 갯수를 체크해줍니다.
- 우선 시작점을 정합니다.
Big O(time)
- O(K * N/K * logN)
Code(x)
#include<bits/stdc++.h>
#define endl '\n'
#define all(v) (v).begin(), (v).end()
#define pb push_back
#define rep(i,a,b) for(int i=a;i<b;i++)
#define r_rep(i,a,b) for(int i=a;i>b;i--)
typedef long long ll;
const int MAXN = 2 * 100 * 1000 + 1;
using namespace std;
vector<int> vec;
int n, coin, k, ans;
int a[MAXN];
ll psum[MAXN];
int bs(int st, int en, int money, int shifts){
int bu = st - 1, ret = 0;
int mid = 0;
while( st <= en ){
mid = (st + en) / 2;
if(psum[mid+shifts] - psum[bu+shifts] <= 1LL *money){
st = mid+1;
ret = max(ret, mid - bu);
}else{
en = mid-1;
}
}
return ret;
}
void solve(int *x, int sz, int money, int cnt){
int shifts = cnt;
rep(i, 0, sz){
if(i+k-1 < sz && x[i+k-1] <= money){
money -= x[i+k-1];
cnt += k;
i += (k-1);
}
else{
if(i == 0){
if(x[i] <= money) cnt++;
}else{
int ret = bs(i, sz-1, money, shifts);
cnt += ret;
}
break;
}
}
ans = max(ans, cnt);
}
void process(){
ans = 0;
cin >> n >> coin >> k;
rep(i, 0, n) cin >> a[i];
sort(a, a+n);
psum[0] = a[0];
rep(i, 1, n) psum[i] = psum[i-1] + a[i];
for(int i = 0; i < k; i++){
if(i == 0){ solve(a + i, n - i, coin, i); continue; }
if(1LL * coin >= psum[i-1]) solve(a + i, n - i, (int)(1LL * coin - psum[i-1]), i);
else break;
}
vec.pb(ans);
}
int main(){
ios_base::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int tc; cin >> tc;
rep(i, 1, tc+1){
process();
}
rep(i, 0, vec.size()) cout << vec[i] << endl;
return 0;
}
Problem C : Petya and Exam
- level : 1800
- tag : greedy, sortings, two pointers
- 문제를 정확히 이해하고 풀어야 하지만, 급할수록 머리가 아닌 손만 바빠지는 것 같습니다.
Point
- n, T, a, b가 주어집니다.
- 문제의 난이도 배열과 해당 문제를 꼭 해당 시간까지 풀어야하는 시간 배열이 주어집니다.
- 이때 최대 몇 문제를 풀 수 있는지 출력합니다.
- 참가자는 1초 단위로 방을 나갈 수 있지만,
- 방을 나가는 시간을 T라고 할때,
- T초까지 꼭 풀어야하는 모든 문제를 풀어야 점수가 인정됩니다.
Design(x)
- 문제를 정확히 이해하지 못하고 비벼보다가 editorial을 참고하여 작성하였습니다.
- 1초마다 방을 탈출할 수 있지만, 모든 시간을 체크하면 바로 TLE 가 발생할 것입니다.
- 따라서, 문제가 mandatory가 되는 시간을 조작해야 한다는 아이디어는 얻었습니다.
- 그래서 mandatory순으로 정렬을 해줍니다.
- 그렇다면, mandatory시간 간격으로 해당 시간까지 mandatory인 것의 갯수만 체크하면 될까요?
- 아닙니다, mandatory인 것을 다 풀고 남는 시간이 있다면 다른 문제도 풀 수 있습니다.
- 또한, mandatory 시간이 같은 문제들이 존재할까요?
- 존재합니다.
- 그렇기에, 이걸 어떻게 구현해주어야 할지 정말 고민을 많이하였지만, edit은 정말 간단하게 처리합니다.
- 풀이법은 이렇습니다.
- 일단 전체 a문항의 수와 b문항의 수를 세어놓습니다.
- 그리고 각 mandatory문항들을 훑습니다.
- 이때, 해당 문항까지 올때까지 a문항수와 b문항수를 세어야 합니다.
- 그래야 여기서 사용할 수 있습니다.
- 이 필수항목들의 합을 구합니다.
- 그리고, mandatory time에서 필수항목들의 합을 뺴고 1도 뺀 값이 0이상인지 확인합니다.
- 즉, mandatory time 1초전에 방을 나가는 경우에 대한 시뮬레이션입니다.
- 가능하다면,
- 이제, 필수 과목이 아닌 것들 중 가능한 많은 문제를 풀어야 합니다.
- 그렇게 되려면 a부터 풀고 b를 푸는 것이 맞겠습니다.
- 이후, mandatory time이 같은 것들에 대해서 while문을 돌려주며 처리해줍니다.
Big O(time)
Big O(memory)
Code(x)
#include<bits/stdc++.h>
#define endl '\n'
#define pb push_back
#define all(v) (v).begin(), (v).end()
#define rep(i,a,b) for(int i=a;i<b;i++)
const int MAXN = 2 * 100 * 1000 + 1;
typedef long long ll;
using namespace std;
int n, T, a, b;
ll allA, allB;
int hard[MAXN];
vector<pair<ll, ll> > mNh;
void input(){
// init
allA = allB = 0;
mNh.clear();
cin >> n >> T >> a >> b;
rep(i, 0, n){
cin >> hard[i];
if(hard[i]) allB++;
else allA++;
}
rep(i, 0, n){
int x; cin >> x;
mNh.pb({x, hard[i]});
}
mNh.pb({T+1, 0});
sort(mNh.begin(), mNh.end());
}
void process(){
input();
ll cntA = 0, cntB = 0, ans = 0;
rep(i, 0, n+1){
ll mandatory = cntA * a + cntB * b;
ll leftT = mNh[i].first - 1 - mandatory;
if(leftT>=0){
int ableA = min(allA - cntA, leftT/a);
leftT -= a * ableA;
int ableB = min(allB - cntB, leftT/b);
leftT -= b * ableB;
ans = max(ans, cntA + cntB + ableA + ableB);
}
int l = i;
while(l < (int)mNh.size() && mNh[i].first == mNh[l].first){
if(mNh[l].second) cntB++;
else cntA++;
l++;
}
i = l-1;
}
cout << ans << endl;
}
int main(){
//freopen("readme.txt","r",stdin);
ios_base::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int tc; cin >> tc;
rep(i, 1, tc+1){
process();
}
return 0;
}