+ 본선에 진출하였습니다.
원래는 ICPC 팀으로 나가려 했으나 jinhan814가 본선 당일 개인 일정이 있어 tmdghks과 같이 나갔습니다. 예선은 저와 eoaud0108은 학교에서 같이 하기로 했고, tmdghks은 다른 장소에서 디스코드를 통해 소통하기로 했습니다.
A가 가장 쉬운 문제임은 이미 공지 되어 있는 상태여서, A는 제가 빠르게 풀기로 했습니다. eoaud0108는 앞부터 차례로 본다고 했고, tmdghks은 쉬운 문제를 찾는다고 한것 같은데, 정확히 기억나지는 않네요.
우선 계획대로 A를 봤습니다. 지문은 길어보여서 일단 입출력을 읽었습니다. 입력을 보니 직사각형 모양이 주어지는 것 같고, 출력을 보니 안에 들어가는 원의 최대 반지름을 구하라는 것 같아 예제를 보니 맞아보였습니다. 단위를 굵게 칠해 준 것도 도움이 되었습니다. 보자마자 풀어 코딩에 들어갔습니다.
$\min(h,w)\times50$을 출력하면 됩니다.
#include<bits/stdc++.h>
using namespace std;
#ifdef kidw0124
constexpr bool ddebug = true;
#else
constexpr bool ddebug = false;
#endif
#define debug if constexpr(ddebug) cout << "[DEBUG] "
void solve(){
int h,w;
cin>>h>>w;
cout<<min(h,w)*50<<'\n';
}
int main() {
#ifdef kidw0124
freopen("input.txt","r",stdin);
freopen("output.txt","w",stdout);
clock_t st=clock();
#else
ios_base::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#endif
int t=1;
// cin>>t;
while(t--)solve();
#ifdef kidw0124
debug<<clock()-st<<"ms\n";
#endif
}
이 문제를 풀고 B는 eoaud0108가 보고 있어서 저는 C를 봤습니다. 그림을 보는데 얼마 전 Solved.ac 마라톤에서 본 BOJ 24536 - 정원장어문제와 같은 문제인 줄 알고 제가 잡겠다고 했습니다.
이 사이 tmdghks이 E가 쉬운 문제인 것 같다고 하였고, 동시에 J가 상당히 많은 틀린 제출이 있다는 것을 확인했습니다. eoaud0108이 E를 보더니 그리 쉬운 문제는 아닌 것 같다고 하였고, tmdghks와 eoaud0108이 J는 그리디하게 하면 안되는지, 그렇다면 저렇게 많은 오답 제출이 있을 것 같지 않다고 얘기하며 다른 문제를 보고 있었습니다.
그러는 동안 제가 잡은 C가 기존의 문제와 같은 문제가 아님을 알았고, 스코어보드에 E가 풀린 것을 확인했습니다. 그리고 tmdghks가 E가 브루트포스인 것 같다고 하였고, 저도 브루트포스로 될 것 같아서 풀었습니다.
과제가 $N$개가 있고, 각각 기한이 있습니다. 과제 하나를 하는 데는 $A$만큼 시간이 걸립니다. 실버(문제 주인공)은 딱 한 번 원하는 시각에 원하는 $X(1\le X \le A-1)$를 골라 $BX$ 만큼 자고, 이후 과제를 하나 하는데 걸리는 시간을 $A-X$로 만들 수 있습니다. 과제를 최대한 많이 하는 개수를 구하는 문제입니다.
$N, A, B$의 범위가 $100$이하입니다. 우선 주어진 과제의 마감 기한을 정렬합니다. 과제별 가중치가 없으므로 과제를 끝낼 수 있다면 가능한 앞쪽 과제를 끝내는 것이 이득입니다. 잠에 들기 전 $i$개의 과제를 끝내거나 포기하고, $j$만큼 자고 일어났을 때, 얼마나 많은 과제를 할 수 있을 지 구합니다. $i$와 $j$가 정해지면, 그리디하게 배정하면 되므로 $O(N)$에 $i$, $j$에서 최대값을 구할 수 있습니다. 따라서 $O(N^2A)$에 풀 수 있습니다.
#include<bits/stdc++.h>
using namespace std;
#ifdef kidw0124
constexpr bool ddebug = true;
#else
constexpr bool ddebug = false;
#endif
#define debug if constexpr(ddebug) cout << "[DEBUG] "
void solve(){
int i,j,k;
int n,a,b;
cin>>n>>a>>b;
vector<int>trr(n+1);
for(i=1;i<n+1;i++){
cin>>trr[i];
}
sort(trr.begin(), trr.end());
int ans=0;
for(i=0;i<n;i++){
for(j=0;j<a;j++){
int tmp=0,now=0;
for(k=1;k<=i;k++){
if(now+a<=trr[k]){
now+=a;
tmp++;
}
}
now+=b*j;
for(k=i+1;k<=n;k++){
if(now+(a-j)<=trr[k]){
now+=a-j;
tmp++;
}
}
ans=max(ans,tmp);
}
}
cout<<ans<<'\n';
}
int main() {
#ifdef kidw0124
freopen("input.txt","r",stdin);
freopen("output.txt","w",stdout);
clock_t st=clock();
#else
ios_base::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#endif
int t=1;
// cin>>t;
while(t--)solve();
#ifdef kidw0124
debug<<clock()-st<<"ms\n";
#endif
}
E를 구현하는 중 C, D, G, H, J가 풀렸습니다. eoaud0108이 D는 쉬워보인다고 해서 잡기로 했고, tmdghks는 나머지 문제들을 생각하기로 했습니다.
제가 E를 AC를 띄우고 J를 보니 그리디하게 될 것 같아 잡았습니다. 동전이 일렬로 주어질 때 “연속된”, “같은” 두개의 동전을 뒤집어 모두 앞면을 보게 하는 최소 시행을 구하는 것인데, 제가 “연속된”만 보고 “같은”을 보지 않고 제출해 한 번 틀렸습니다.
다시 돌아와서 J가 해당 방식으로 되지 않는 다는 것을 알고 일단 다른 문제를 읽기 시작했습니다. C는 아까 생각해봤었고, D는 eoaud0108가 풀고 있으니, G를 봤습니다. G는 문제는 이해 됐는데, 도저히 머리로 입체가 그려지지 않아 일단 넘어갔고, G에서 머리가 띵해져서 H를 읽기보다 그냥 J를 다시 생각하기로 했습니다.
그 사이 eoaud0108이 D를 풀어 제출했으나, WA를 받았습니다. 단순 세그로 풀린다는데, WA가 왜 떴는 지 모르겠다고 하며 eoaud0108가 D 틀린 것을 찾기로 했고, tmdghks은 G를 그냥 하면 될 것 같다며 G를 풀기로 했습니다.
저는 J를 그리디였으면 다른 팀들이 다 풀었을 텐데, HH
를 뒤집어야 하는 예시가 있나 생각하다가 THTTHT
라는 기깔난 예시를 찾았습니다. 그리고 이는 재귀적으로 풀리겠다 생각했으나, TLE가 나는 것이 분명해 보여 해당 문제 더 생각이 나지 않아 그냥 아까 C를 생각하던 것이 있어 다시 보기로 했습니다. 나중에 통계를 통해 알게 되었는데, J는 WA보다 TLE가 월등히 많이 제출된 문제였습니다. 그러니까 제가 이 때 “그리디가 아니여서 다른 팀들이 틀렸을거야”라고 생각한 것은 사실 아니였고 다른 팀들은 시뮬레이션처럼 진행해 그랬던 것 같습니다.
이 때 eoaud0108가 D 코드에서 세그를 구성할 때 1<<18
의 크기로 구성했고, 이것이 범위가 작아 설마 이것 때문에 틀리나 싶어 다시 제출한다 했는데, 그 설마가 맞았어서 허무하게 D를 맞았습니다.
D - 이진 검색 트리 복원하기 [+1, 0:50:57]
저는 C를 풀고 있고, tmdghks는 G를 풀고 있고, H가 많이 풀려 eoaud0108가 H를 보기로 했습니다. H를 보더니 eoaud0108가 만조분이라고 하며 이를 풀기로 했습니다.
10분 쯤 지나고 eoaud0108가 H를 제출하며 이건 WA 많이 뜰수도 있다고 했습니다. 예상대로 첫 제출은 WA가 떴고 20분 뒤 두 번째 제출도 WA가 떴습니다.
그 사이 저는 C코딩을 끝냈고, 제출했으나 WA가 나왔습니다. L과 R이 나뉘는 지점을 기준으로 모두 돌았는데, WA가 떠서 혹시나 풀이가 틀렸나 열심히 고민하던 중, LR이 안나뉘고 모두 R이거나 모두 L일 수 있음을 깨달아 이를 고쳐 제출하니 AC가 떴습니다. 자세한 설명은 아래에 있습니다.
왼쪽 혹은 오른쪽을 보고 있는 미어캣 $N$마리가 있습니다. $N$마리의 미어캣은 서로 다른 위치에 일렬로 서 있고, 키가 서로 다릅니다. 같은 방향을 보고 있는 미어캣들의 순서를 재배치하는 것은 가능합니다. 다만 L과 R의 배치는 유지되어야 합니다. 즉, 서로 다른 방향을 보고 있는 미어캣들의 순서를 바꾸는 것은 불가능합니다.(이것이 정원장어와 다른 점입니다)
최종적으로 각 미어캣에 대해 자신이 바라보는 방향에 자신보다 키가 큰 미어캣이 없는 경우 망을 볼 수 있습니다. 이때 망을 볼 수 있는 미어캣의 최대 수를 구하는 문제입니다.
정원장어의 아이디어를 그대로 갖고 올 수 있습니다. 망을 보는 미어캣은 LL...LLRR...RR
의 순서로, L
에서는 오름차순 R
에서는 내림차순의 키를 가지고 있어야 합니다. RL
이 있는 경우 둘 중 키가 작은 미어캣은 다른 미어캣에 막히게 됩니다. 즉, LR
의 지점이 최대 $N$개 있을 수 있고, 이를 고정시켰다고 생각해봅니다.
기준점을 기준으로 왼쪽 L
의 경우 망을 최대한 봐야 하므로 키가 커야 하며, 오른쪽 L
의 경우 최대한 키가 작아야 합니다. 즉, 기준점 바로 옆 L
에 가장 키가 큰 것을 놓고, 왼쪽으로 가며 차례로 내림차순으로 키가 작아지도록 배치합니다. 그리고 기준점 오른쪽 R
의 경우 최대한 많이 봐야 하므로, 기준점 오른쪽의 L
은 오른쪽으로 갈 수록 키가 작아지게 하면 됩니다. 만약 이 경우 R
이 L
에 막혀 보지 못한다면 L
의 순서를 바꾸면 그 R
은 계속 못보며 새로운 못보는 것이 생길 수 있으므로 이것이 이득입니다. 정리하자면, 기준점을 기준으로 왼쪽에 가장 키가 큰 L
을 놓고, 왼쪽으로 가면서 키가 큰 순서대로 L
을 차례로 놓아 준 뒤, 다시 기준점으로 와서 오른쪽으로 가며 키가 큰 순서대로 L
을 배치하면 됩니다.(ex> 기준점이 |
라면 5678|4321
) R
은 반대로 배치해 줍니다.
모든 기준점이 $O(N)$개 있고, 각 기준점 별로 그리디하게 배치하면 $O(N)$에 해결할 수 있으므로, 총 시간복잡도 $O(N^2)$에 풀 수 있습니다. 정렬의 시간복잡도는 $O(N\lg N)$이므로, 답보다 작습니다.
이때, RR....RR
과 LL....LL
의 경우도 고려해주어야 합니다. 구현 상에서 $0$이상 $N$이하로 해주면 됩니다. 이 경우를 고려하지 않아 $0$이상 $N$미만으로 구현해 처음에 WA가 떴습니다.
+ 기여를 보니까 정렬 안하고 투포인터로 풀 수 있는 듯 합니다.
+ 정렬을 안하고 투포인터가 아니라 정렬 후 선형 시간에 풀 수 있다는 것 이라고 합니다.
#include<bits/stdc++.h>
#pragma comment(linker, "/STACK:336777216")
#pragma GCC optimize("O3,unroll-loops")
#pragma GCC target("avx,avx2,fma")
using namespace std;
#ifdef kidw0124
constexpr bool ddebug = true;
#else
constexpr bool ddebug = false;
#endif
#define debug if constexpr(ddebug) cout << "[DEBUG] "
void solve(){
int i,j,k;
int n,a,b;
cin>>n;
vector<pair<int,int>> loc(n);
vector<int> lef,rig;
for(i=0;i<n;i++){
cin>>loc[i].first;
char dir;
cin>>dir;
if(dir=='L')lef.push_back(loc[i].first),loc[i].second=0;
else rig.push_back(loc[i].first),loc[i].second=1;
}
if(lef.empty()||rig.empty()){
cout<<n<<'\n';
return;
}
sort(lef.begin(), lef.end());
sort(rig.begin(), rig.end());
int ans=0;
for(i=-1;i<n;i++){
vector<int> tmp(n);
int nowl=lef.size()-1,nowr=rig.size()-1;
for(j=i;j>=0;j--){
if(loc[j].second==0){
tmp[j]=lef[nowl--];
}
}
for(j=i+1;j<n;j++){
if(loc[j].second==1){
tmp[j]=rig[nowr--];
}
}
for(j=i;j>=0;j--){
if(loc[j].second==1){
tmp[j]=rig[nowr--];
}
}
for(j=i+1;j<n;j++){
if(loc[j].second==0){
tmp[j]=lef[nowl--];
}
}
int now=0;
vector<int>maxlef(n),maxrig(n);
maxlef[0]=tmp[0];
for(j=1;j<n;j++){
maxlef[j]=max(maxlef[j-1],tmp[j]);
}
maxrig[n-1]=tmp[n-1];
for(j=n-2;j>=0;j--){
maxrig[j]=max(maxrig[j+1],tmp[j]);
}
for(j=0;j<n;j++){
if(loc[j].second==0){
if(j==0||tmp[j]>maxlef[j-1]) now++;
}
else{
if(j==n-1||tmp[j]>maxrig[j+1]) now++;
}
}
ans=max(ans,now);
}
cout<<ans<<'\n';
}
int main() {
#ifdef kidw0124
freopen("input.txt","r",stdin);
freopen("output.txt","w",stdout);
clock_t st=clock();
#else
ios_base::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#endif
int t=1;
// cin>>t;
while(t--)solve();
#ifdef kidw0124
debug<<clock()-st<<"ms\n";
#endif
}
이걸 풀고 나서 eoaud0108가 혹시 H에서 자기가 빼먹은 케이스가 있는 지 확인해 달라고 하며 저에게 문제를 설명하고 케이스들을 설명하고 고민하는 과정에서 하나의 케이스가 더 있음을 발견했습니다. 이를 풀어서 제출하니 AC가 떴습니다.
부호가 같은 보관함은 가장 원점에서 먼 보관함만 보면 됩니다.
이 경우를 모두 봐 최소를 출력합니다. 정렬을 하거나 최소/최대를 찾아야 하므로 $O(N\lg N)$ 혹은 $O(N)$에 풀 수 있습니다. 범위가 int
를 넘어갈 수 있으므로 long long
으로 처리해야 합니다.
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
#ifdef kidw0124
constexpr bool ddebug = true;
#else
constexpr bool ddebug = false;
#endif
#define debug if constexpr(ddebug) cout << "[DEBUG] "
void solve(){
int i,j,k;
int n,d;
cin>>n>>d;
vector<ll> arr(n);
for(ll &x:arr)cin>>x;
sort(arr.begin(),arr.end());
if(arr.front()*arr.back()>=0){
cout<<max(abs(arr.front()),abs(arr.back()))*2+d<<'\n';
}
else{
cout<<min({
-arr.front()*2+arr.back()*2+d*2,
-arr.front()*4+arr.back()*2+d*1,
-arr.front()*2+arr.back()*4+d*1,
(arr.back()-arr.front())*2>=d?
(arr.back()-arr.front())*4:
-arr.front()*2+arr.back()*2+d,
})<<'\n';
}
}
int main() {
#ifdef kidw0124
freopen("input.txt","r",stdin);
freopen("output.txt","w",stdout);
clock_t st=clock();
#else
ios_base::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#endif
int t=1;
// cin>>t;
while(t--)solve();
#ifdef kidw0124
debug<<clock()-st<<"ms\n";
#endif
}
이제 남은 문제는 B, F, G, I, J, K였습니다. G는 tmdghks이 보고 있고, 솔브 수를 보니 반드시 J를 풀어야겠다고 생각했습니다. 제가 J를 아까부터 보고 있었어서 J를 보고, eoaud0108이 남은 문제를 읽고 생각하고 있었습니다.
그러다가 tmdghks이 G 케이스가 $81$가지인데, 정리하는 것 좀 도와달라고 해 eoaud0108이 도와주기로 했습니다.
저는 J에서 어떤 T
를 발견하면 다음 연속된 TT
혹은 HH
를 뒤집어야 겠다 생각했습니다. 더불어 그렇다면 사이에 반복되어 있을 것이며, THTHTHTHTHTT
를 뒤집게 되면 하나씩 밀려 결국 HHTHTHTHTHTH
가 됨을 알았습니다. 그래서 연속된 구간을 set에 저장해두고, 앞에서 부터 T
를 만나면 다음 구간에서 뒤집고, 길이만큼 더한 뒤 다시 넣는 방식으로 반복하는 코드를 작성해 제출했으나 WA가 나왔습니다. 나중에 풀이를 알고 보니 해당 set을 depth마다 만들면 정해의 stack에서 size랑 대응되어 동치인 풀이가 나온다는 것을 알게 되었습니다.
일단 WA가 나온 시점에서 저는 J에서 아이디어를 떠올리기 힘들다고 생각을 했고, 아까 나온 “재귀”의 아이디어를 분할정복으로 바꾸어 풀 수 있을 지 고민했습니다. 그래서 대충 생각했으나 THHHT
가 NO
가 나온다는 것을 알고, 가능/불가능 판정이 해당 방식으로는 어려울 것 같아 해당 방식은 버리게 되었습니다. 이 시점에서 J는 제 문제가 아닌가보다 하고 eoaud0108에게 넘기기로 하였습니다.
남은 B, F, I, K 중 B, F, I는 지문 읽으면서 어려운 스멜이 났고, K는 그나마 구성적이여서 할만해 보였습니다. 실제로 스코어보드도 G, J를 제외하면 K처럼 보여 K를 잡기로 했습니다. eoaud0108가 읽었어서 문제를 설명해주었고, 저는 다른 제한보다 $A+B$가 변의 길이 이하라는 제한에 집중해보기로 했습니다.
우선 홀수 차수가 홀수 개면 안되는 것은 자명했고, 일렬로 쭉 나열한 것을 생각해보니 홀수가 $2$개 있는 경우는 항상 가능함을 알 수 있었습니다. 더 나아가 홀수가 $2$개 이상일 때는 반드시 만드는 경우가 있음을 알았고, 남은 경우는 홀수가 $0$개 있을 때 짝수가 문제였습니다. 자세한 설명은 아래 풀이에 적어두었습니다.
대충 이 시점에서 스코어보드는 프리즈 되었습니다.
프리즈 시점에 5솔 1등으로 42등이였기 때문에, 패널티는 저희 팀도 관리를 잘 못했지만, 전체적으로 관리가 어렵다는 판단을 했고, 못해도 1솔브는 더, 웬만하면 2솔브는 더 풀어야겠다고 생각했습니다. G번의 경우 맞는 것을 바라는 것은 너무 도박일 수 있었기 때문에, J와 K를 푸는 것을 목표로 했습니다. K는 거의 풀렷다고 생각했기 때문에 이 시점에서 tmdghks에게 부담을 가지지 말라고 했고, G는 맞으면 좋다의 마인드로 접근했습니다.
그리고 조금 더 생각하다가 K에서 가능한 경우들을 찾아 풀었습니다.
우선 홀수가 $2$개 이상 있다면 아래와 같이 배치할 수 있습니다. 우선 양 끝에 홀수 차수 점 $2$개를 배치한 다음 남는 홀수 차수는 짝수개 이므로, 지그재그로 배치합니다. 그리고 짝수들에 대해서는 차수가 2가 되도록 일렬로 배치해주면 됩니다.
.O.O.O.......
OOOOOOOOOOOOO
..O.O.O......
K에서 홀수가 $0$개 있을 때 짝수를 몇개 채우는 것을 고민해봤습니다. 우선 ㅁ모양으로 채우면 4개가 가능하며, 예제처럼 ㅁ에 끝 모서리에 $3$개씩 더해가면 $3k+1$꼴이 된다는 것을 알았습니다. 덤으로 ㅁ도 점 하나에서 $3$개를 더했다고 생각했습니다.
O OO OO OO
OO OOO OOO
OO OOO
OO
또한 $8$, $12$일 때 아래 모양 처럼됨을 알았습니다. 나중에 알았는데 $8$은 예제에 있었습니다. 이거에서 $3$개씩은 붙여나갈 수 있으므로 8이상의 $3k+2$, 12이상의 $3k$꼴은 모두 됨을 알 수 있습니다.
OOO OOOO OOO
O.O O..O O.O
OOO O..O OOOO
OOOO OO
추가로, 이걸 짜고 제출 후에 생각난 방식으로 아래와 같이 $8$이상의 짝수를 모두 만들 수도 있습니다. 또한 오른쪽 아래에 3개를 붙이는 방식으로 $11$이상의 홀수도 모두 만들 수 있습니다.
OOO OOOO OOOOO
O.O O..O O...O
OOO OOOO OOOOO
남는 수는 $2$, $3$, $5$, $6$, $9$인데, $9$는 예제에 안된다고 주어져 있고, 나머지는 아무리 해봐도 안되길래 확신의 믿음을 가지고 제출했습니다.
그러나 WA가 나오길래 아무리 봐도 맞는데 구현 실수인가 하다가 YES
인 경우에 YES
와 행과 열의 크기를 출력하지 않았음을 알아 다시 제출해 AC를 띄웠습니다. 시간복잡도는 유의미하지 않지만 $O(N^2+A+B)$입니다. $N$은 $200$으로 고정했습니다.
#include<bits/stdc++.h>
using namespace std;
#ifdef kidw0124
constexpr bool ddebug = true;
#else
constexpr bool ddebug = false;
#endif
#define debug if constexpr(ddebug) cout << "[DEBUG] "
void solve(){
int i,j,k;
int n,m;
cin>>n>>m;
vector<string> arr(200,string(200,'.'));
if(m==0){
if(n%3==1){
arr[0][0]='O';
i=1;
for(j=0;j<(n-1)/3;j++){
arr[i][i-1]='O';
arr[i][i]='O';
arr[i-1][i]='O';
i++;
}
}
else if(n%3==2){
if(n<8){
cout<<"NO\n";
return;
}
arr[0][0]=arr[0][1]=arr[0][2]
=arr[1][0] =arr[1][2]
=arr[2][0]=arr[2][1]=arr[2][2]='O';
i=3;
for(j=0;j<(n-8)/3;j++){
arr[i][i-1]='O';
arr[i][i]='O';
arr[i-1][i]='O';
i++;
}
}
else{
if(n<12){
cout<<"NO\n";
return;
}
arr[0][0]=arr[0][1]=arr[0][2]=arr[0][3]
=arr[1][0] =arr[1][3]
=arr[2][0] =arr[2][3]
=arr[3][0]=arr[3][1]=arr[3][2]=arr[3][3]='O';
i=4;
for(j=0;j<(n-12)/3;j++){
arr[i][i-1]='O';
arr[i][i]='O';
arr[i-1][i]='O';
i++;
}
}
}
else if(m&1){
cout<<"NO\n";
return;
}
else{
arr[0][2]='O';
i=1;
for(j=0;j<(m-1)/2;j++){
arr[i][2]='O';
if(j&1){
arr[i][1]='O';
}
else{
arr[i][3]='O';
}
i++;
}
for(j=0;j<n;j++){
arr[i][2]='O';
i++;
}
arr[i][2]='O';
}
cout<<"YES\n";
cout<<"200 200\n";
for(i=0;i<200;i++) {
cout << arr[i] << '\n';
}
}
int main() {
#ifdef kidw0124
freopen("input.txt","r",stdin);
freopen("output.txt","w",stdout);
clock_t st=clock();
#else
ios_base::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#endif
int t=1;
// cin>>t;
while(t--)solve();
#ifdef kidw0124
debug<<clock()-st<<"ms\n";
#endif
}
#include<bits/stdc++.h>
using namespace std;
#ifdef kidw0124
constexpr bool ddebug = true;
#else
constexpr bool ddebug = false;
#endif
#define debug if constexpr(ddebug) cout << "[DEBUG] "
void solve(){
int i,j,k;
int n,m;
cin>>n>>m;
vector<string> arr(200,string(200,'.'));
if(m==0){
if(n%3==1){
arr[0][0]='O';
i=1;
for(j=0;j<(n-1)/3;j++){
arr[i][i-1]='O';
arr[i][i]='O';
arr[i-1][i]='O';
i++;
}
}
else{
if(n%2==1&&n<11){
cout<<"NO\n";
return;
}
if(n<8){
cout<<"NO\n";
return;
}
int x=n%2;
if(x)n-=3;
for(i=0;i<(n-2)/2;i++){
arr[i][0]=arr[i][2]='O';
}
arr[0][1]='O';
arr[i-1][1]='O';
if(x){
arr[i][2]=arr[i][3]=arr[i-1][3]='O';
}
}
}
else if(m&1){
cout<<"NO\n";
return;
}
else{
arr[0][2]='O';
i=1;
for(j=0;j<(m-1)/2;j++){
arr[i][2]='O';
if(j&1){
arr[i][1]='O';
}
else{
arr[i][3]='O';
}
i++;
}
for(j=0;j<n;j++){
arr[i][2]='O';
i++;
}
arr[i][2]='O';
}
cout<<"YES\n";
cout<<"200 200\n";
for(i=0;i<200;i++) {
cout << arr[i] << '\n';
}
}
int main() {
#ifdef kidw0124
freopen("input.txt","r",stdin);
freopen("output.txt","w",stdout);
clock_t st=clock();
#else
ios_base::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#endif
int t=1;
// cin>>t;
while(t--)solve();
#ifdef kidw0124
debug<<clock()-st<<"ms\n";
#endif
}
이걸 짜는 동시에 eoaud0108가 J풀이를 찾아 구현해 AC를 받았습니다.
우선 T
를 발견했을 때, 뒤에 HH
를 가져오게 되면 HH
가 HT
가 됩니다. 즉, 이는 또다른 T
와 만나야 하므로, 사이에 짝수개가 있는(이렇게 되면 홀짝성에 의해 가운데에 HH
가 반드시 존재합니다) 다음 T
를 발견하는 것이 중요합니다. 즉, 사이에 짝수개인, 인덱스 차이가 홀수인 T
를 발견하면 스택에서 뽑고 아니면 스택에 넣는 것을 하여 최종적으로 스택이 비어있는 지를 검사하면 됩니다. $O(N)$에 풀립니다. 이는 결국 set풀이에서 depth를 준 것과 동치입니다. 답이 int
범위를 넘어갈 수 있으므로 long long
을 사용했습니다.
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
#ifdef kidw0124
constexpr bool ddebug = true;
#else
constexpr bool ddebug = false;
#endif
#define debug if constexpr(ddebug) cout << "[DEBUG] "
void solve(){
int i,j,k;
int n;
cin>>n;
string str;
cin>>str;
stack<int>stk;
ll ans=0;
for(i=0;i<n;i++){
if(str[i]=='T'){
if(stk.empty() || (stk.top()-i)%2==0){
stk.push(i);
}
else{
ans+=i-stk.top();
stk.pop();
}
}
}
if(stk.size())cout<<"-1\n";
else cout<<ans<<'\n';
}
int main() {
#ifdef kidw0124
freopen("input.txt","r",stdin);
freopen("output.txt","w",stdout);
clock_t st=clock();
#else
ios_base::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#endif
int t=1;
cin>>t;
while(t--)solve();
#ifdef kidw0124
debug<<clock()-st<<"ms\n";
#endif
}
이후 마지막 1분을 남기고 tmdghks이 G를 짜 제출했으나 틀렸습니다.
우선 J에서 틀린 풀이는 어쩔 수 없다 쳐도, C에서 끝을 탐색하지 않은 것, 그리고 K에서 말도 안되는 출력을 안한 것으로 패널티를 쌓은 것은 정말 하면 안되었던 것 같습니다. 대회 초반 E나 C구현할 때 엄청 머리 아프고 집중이 되지 않았었는데, 다음부터는 대회 전에 일찍 일어나 머리를 풀어 두어야 하겠습니다.
오늘은 tmdghks가 온라인으로 같이 해 소통을 잘 하지 못했지만, 본선 때 잘 해보도록 하겠습니다.
]]>처음에 아래 그림과 같이 $1$부터 $2^N$까지의 수가 일렬로 적혀있는 종이 조각이 있습니다. 이 종이 조각을 왼쪽 혹은 오른쪽 절반을 위로 접는 것을 $N$번 반복해 각 층마다 $1$개의 종이 조각씩 총 $2^N$층으로 만들었습니다.(매번 왼쪽, 오른쪽을 정할 수 있습니다) 이때, 처음에 $P$번이 아래서부터 $H$번째가 되도록 접는 방법을 찾아 출력하는 문제입니다. 아래 그림은 $2^3$개에서 $4$번이 $7$층이 되도록 LRL
로 접는 방법을 나타냅니다.
예제의 상황인 $N=3$, $P=4$, $H=7$을 생각해 봅시다. 또, $1$부터 $2^N$까지를 비트마스킹을 위해 $0$부터 $2^N-1$로 생각하겠습니다. 그리고 아래와 그림과 같이 빨간 글씨대로 자리의 번호를 정합니다.
이제 최종 마지막 상태에서 역으로 생각해 보면 다음을 알 수 있습니다.
이 자리들을 보면 층수가 같습니다. 따라서 이를 층수에 따라 생각해보겠습니다. 마찬가지로 가장 아래를 $0$층이라 하겠습니다. 층수는 위에 파란 글씨로 표시해두었습니다.
또한, $i$번째 단계에서 층수가 총 $2^{i-1}$개의 층이 있는데, 이 중 절반($2^{i-2}$) 이상이라고 하면 접힌 쪽에서 접혔을 것이며, 이하라고 하면 위쪽이 접혀 올라왔습니다. 즉, 아래쪽이면 원래의 층수를 유지하며, 위쪽이면 원래 층수에서 절반에 해당하는 $2^{i-2}$을 빼준 후 비트를 뒤집어 주면 됩니다. 즉, 최상위 비트를 제거하고 비트를 flip합니다. 예를 들어 9번째의 높이가 이진수로 $11010010$의 경우 최상위 비트를 제거하면 $1010010$이 되고, 이를 flip하면 $0101101$이 8단계의 층이 됩니다. 9번째의 높이가 $01010010$일 경우 그 자리를 유지해(최상위 비트 $0$만 제거) $1010010$이 됩니다.
이를 통해 각 단계에서의 층수를 모두 구할 수 있습니다. 이제 다시 원래에서 접어야 하는데, 우선 현재 위치가 절반보다 왼쪽인지 오른쪽인지 구한 뒤, 만약 다음 단계의 높이가 절반 이상이라면 원하는 종이조각이 있던 곳을 위로 올려야 하므로 해당 방향을, 아니면 그대로 두어야 하므로 반대 방향을 출력하면 됩니다.
비트 플립을 구현할까 하다가 어차피 $x$자리라면 2진수로 $2^x-1$에서 빼면 됨을 통해 그냥 빼서 구현했습니다. 시프트 할 때 1<<x
와 같이 하면 오버플로우가 나니까 1LL<<x
혹은 1ll<<x
로 해야 합니다. 또한, 사용하는 많은 변수를 64비트 정수형으로 선언해야 합니다.
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll, ll> pll;
void solve() {
ll i,j;
ll n,p,h;
cin>>n>>p>>h;
p--,h--;
vector<ll>arr(n);
for(i=0;i<n;i++){
j=(1LL<<(n-i-1));
if(h&j){
arr[i]=1LL<<i;
h=(j<<1)-1-h;
}
}
reverse(arr.begin(), arr.end());
for(i=0;i<n;i++){
j=(1LL<<(n-i-1));
if((p&j)==arr[i]){
cout<<'R';
if(p&j) {
p=(j<<1)-1-p;
}
}
else{
cout<<'L';
if(p&j){
p^=j;
}
else{
p=(j-1-p);
}
}
}
}
int main() {
#ifndef ONLINE_JUDGE
freopen("input.txt", "r", stdin);
freopen("output.txt", "w", stdout);
#else
cin.tie(0)->sync_with_stdio(0);
#endif
int t = 1;
//cin >> t;
while (t--) solve();
}
$N$명의 사람들이 있고, 각자 돈을 빌릴 수 있는 두 명이 주어집니다. 이때, $N$명의 사람 중 한 명에게 외부에서 돈을 빌려달라고 요청합니다. 이제 다음을 반복합니다.
이제, $N$명의 사람 각각에 대해 모든 경우에 있어 돈을 잃는 경우가 없다면 N
을, 그렇지 않고 한 가지 경우라도 돈을 잃을 수 있다면 Y
를 출력합니다.
모든 정점의 out-degree가 $2$인 $N$개의 정점으로 이루어진 방향 그래프가 주어집니다. 모든 $i$번 정점에 대해 원하는 하나의 정점에서 시작해 원하는 순서로(이미 탐색된 정점들의 인접 정점들 중 임의로 다음 정점을 고름) 탐색을 할 때, $i$번 정점을 탐색하였을 때, $i$번 정점에서 나가는 방향으로 직접 연결된 두 정점이 이미 탐색된 상태로 할 수 있는지 판단하는 문제입니다.
$i$번 정점에서 나가는 두 정점 $X_i, Y_i$에 대해 단 하나의 경우라도 $X_i, Y_i$가 모두 탐색되었는 지 보면 됩니다. 즉, $i$번 정점에 대한 최악의 경우를 생각하면, 어떤 정점에서 탐색을 시작하여 최대한 늦게 마지막으로 $i$번 정점을 탐색하였을 때, $i$번 정점에서 나가는 두 정점이 모두 탐색되게 할 수 있는 지 판단하면 됩니다.
즉, 이는 다음과 같은 조건으로 바뀌게 됩니다.
모든 $i$번 정점에 대해 순서대로 다음 조건을 만족하는 $v_i\ne i$가 존재하면
Y
를, 그렇지 않으면N
을 출력합니다.
- $v_i$에서 출발해서 $i$번 정점을 탐색할 수 있어야 합니다.
- $v_i$에서 출발해서 $i$번 정점을 거치지 않고 $X_i$와 $Y_i$를 모두 탐색할 수 있어야 합니다.
이를 나이브하게 구현한다면 $O(N)$개의 정점에 대해, $O(N)$개의 출발 정점을 설정하여 $O(N)$의 DFS 혹은 BFS를 수행해야 하므로, $O(N^3)$의 시간복잡도를 가지게 됩니다. 그러나 시간 제한이 $0.5$초이므로 시간 복잡도를 줄여야 합니다.
정점 $a$에서 출발하여 $b$번 정점을 거치지 않고 $c$에 도착할 수 있는 지를 판단하는 것은 역방향 간선 그래프에서, $c$번 정점에서 출발하여 $b$번 정점을 거치지 않고 $a$에 도착할 수 있는 지를 판단하는 것과 동일합니다. $c$와 $b$가 정해져 있다면, 탐색 가능한 모든 $a$를 $O(N)$의 시간 복잡도에 한번에 찾을 수 있습니다.
즉, 위의 조건을 역방향 그래프 상에서 다음 동치 명제로 바꿀 수 있습니다.
모든 $i$번 정점에 대해 순서대로 다음 조건을 만족하는 $v_i\ne i$가 존재하면
Y
를, 그렇지 않으면N
을 출력합니다.
- $i$번 정점에서 출발하여 $v_i$에 도착할 수 있어야 합니다.
- $X_i$에서 출발해서 $i$번 정점을 거치지 않고 $v_i$에 도착할 수 있어야 합니다.
- $Y_i$에서 출발해서 $i$번 정점을 거치지 않고 $v_i$에 도착할 수 있어야 합니다.
즉, 역방향 간선 그래프를 만들어, $i$번 정점에 대하여 $i$, $X_i$, $Y_i$에 대해 DFS를 수행하여 얻은 세개의 정점집합의 교집합이 공집합이 아니라면 Y
를, 그렇지 않으면 N
을 출력하면 됩니다. $O(N)$개의 정점에 대해, $3$개의 출발 정점을 설정하여 $O(N)$의 DFS 혹은 BFS를 수행해야 하므로, $O(N^2)$의 시간복잡도에 문제를 해결할 수 있습니다.
먼저 그래프를 받고 역방향 그래프를 만들었습니다. 이후 시작 정점과 제외할 정점을 인자로 받아, 시작 정점에서 출발하여 제외할 정점을 제외하고 도달할 수 있는 정점들을 반환하는 dfs 함수를 작성하였습니다. 제외할 정점이 없을 때는 -1을 인자로 넘겨주었습니다.
#include <bits/stdc++.h>
using namespace std;
void solve() {
int i,j;
int n;
cin>>n;
vector<pair<int,int>>grp(n+1);
vector<vector<int>>rgrp(n+1);
string ans;
for(i=1;i<=n;i++){
cin>>grp[i].first>>grp[i].second;
rgrp[grp[i].first].push_back(i);
rgrp[grp[i].second].push_back(i);
}
function<vector<bool>(int,int)> dfs=
[&rgrp,&n](int s, int inter)->vector<bool>{
stack<int>stk;
stk.push(s);
vector<bool>vis(n+1);
vis[s]=1;
while(stk.size()){
int now=stk.top();
stk.pop();
for(auto k:rgrp[now]){
if(vis[k]||k==inter)continue;
else{
vis[k]=1;
stk.push(k);
}
}
}
return vis;
};
for(i=1;i<=n;i++){
auto vis1=dfs(grp[i].first,i);
auto vis2=dfs(grp[i].second,i);
auto vis3=dfs(i,-1);
for(j=1;j<=n;j++){
if(vis1[j]&&vis2[j]&&vis3[j]){
ans+='Y';
break;
}
}
if(j==n+1)ans+='N';
}
cout<<ans;
}
int main() {
#ifdef kidw0124
freopen("input.txt", "r", stdin);
freopen("output.txt", "w", stdout);
#else
cin.tie(0)->sync_with_stdio(0);
#endif
int t = 1;
//cin >> t;
while (t--) solve();
}
바로 전날에 현대모비스 1차와 팀연습을 좋지 않게 마무리하고 Atcoder를 시작했습니다.
R
, M
, S
가 하나씩 있는 길이 $3$의 문자열이 주어질 때, R
이 M
보다 먼저인지 검사하는 문제입니다.
abc A번 치고는 구현할 것이 있는(?) 문제였습니다. 매번 어떤 문자열과 같은지 비교하시오 이런거 나오다가 R
의 위치와 S
의 위치를 찾아야 하는 문제였습니다. string::find
를 사용하여 간단히 풀 수 있었습니다.
void solve(){
ll i,j,k;
string str;
cin>>str;
if(str.find('R')<str.find('M')){
yes();
}
else{
no();
}
}
두개의 문자열 $S$와 $T$가 주어집니다. $S$를 길이 $w$씩 잘라서 이차원으로 적고 이를 세로로 읽을 때, 한 열이라도 $T$와 같은 문자열이 있는지 확인하는 문제입니다. 이 때, $w$는 $S$의 길이 \textbf{미만}입니다. 대회 끝나고 알았는데, $\lvert T\rvert < \lvert S\rvert$라는 조건이 처음에 적혀있었으나, 실제로는 $\lvert T\rvert\le \lvert S\rvert$였습니다. 이 이슈가 1시간 넘게 지난 후 수정되었고, 이것 때문에 WA받은 사람이 있어 어떻게 처리할 지 atcoder가 고민하고 있고, Rating 반영이 많이 늦게 되었습니다. 저는 예제를 보고 이해한지라 해당 이슈가 있는 지 몰랐습니다.
문자열 길이가 $100$이므로 가능한 모든 경우를 해줍니다. $O(\lvert S \rvert^2)$에 해결가능합니다.
void solve(){
ll i,j,k;
string s,t;
cin>>s>>t;
ll n=s.size();
for(i=1;i<n;i++){
vector<string> vct(i);
for(j=0;j<n;j++){
vct[j%i]+=s[j];
}
if(find(all(vct),t)!=vct.end()){
yes();
return;
}
}
no();
}
$N$개의 물건이 있고, 각각의 무게와 어떤 상자에 들어있는지 주어집니다. 각 상자별로 물건을 한 개만 남기고 다 빼고 싶을 때, 빼야 하는 무게의 합을 구하는 문제입니다.
그냥 각 상자별로 무게를 정렬하고, 가장 무거운 것을 제외한 나머지를 더해주면 됩니다. $O(N\log N)$에 해결가능합니다. 개인적으로 B번보다 쉬운 것 같습니다.
굳이 정렬을 하지 않아도, 각 상자별로 가장 무거운 것을 합한 후 전체 합에서 빼주면 $O(N)$에 해결가능합니다.
void solve(){
ll i,j,k;
ll n;
cin>>n;
vector<ll> arr(n),w(n);
vector<vector<ll>>rarr(n+1);
for(i=0;i<n;i++){
cin>>arr[i];
}
for(i=0;i<n;i++){
cin>>w[i];
}
for(i=0;i<n;i++){
rarr[arr[i]].pb(w[i]);
}
for(i=1;i<=n;i++){
sort(all(rarr[i]));
}
ll ans=0;
for(i=1;i<=n;i++){
for(j=0;j+1<rarr[i].size();j++){
ans+=rarr[i][j];
}
}
cout<<ans<<'\n';
}
개미들이 일차원 수직선 위 서로 다른 위치에서 왼쪽 혹은 오른쪽을 보고 있습니다. $1$초에 $1$만큼 움직일때, $T+0.1$초 후 만나는 개미 쌍의 수를 구하는 문제입니다. 개미는 서로 만나더라도 서로 통과해 지나갑니다.
왼쪽을 보는 개미와 오른쪽을 보는 개미는 서로 만날 수 있지만, 서로 같은 방향을 보는 개미는 만나지 않습니다. 우선 예제는 좌표가 다 정렬되어 주어지지만, 실제로는 정렬되지 않은 데이터가 주어지기에 좌표를 먼저 정렬합니다. 이후 좌표가 증가하는 순서대로 개미를 보며, 오른쪽을 보는 개미라면 덱에 넣어주고, 왼쪽을 보는 개미라면 현재 덱에 들어있는 가장 왼쪽의 개미(덱의 front)가 $2T$이하의 거리에 있도록 pop_front를 해줍니다. 그러면 덱에는 현재 좌표에서 $2T$이내에 있는 오른쪽을 보는 개미들만 들어있게 됩니다. 즉, 덱의 크기를 더해주면 됩니다. 정렬에 $O(N\log N)$, 이후 $O(N)$에 해결가능합니다.
void solve(){
ll i,j,k;
ll n,m;
string str;
cin>>n>>m>>str;
vector<ant> ants(n);
for(i=0;i<n;i++){
cin>>ants[i].x;
ants[i].dir=str[i]-'0';
}
sort(all(ants));
ll ans=0;
deque<ll>lef;
for(i=0;i<n;i++){
if(ants[i].dir==1){
lef.push_back(ants[i].x);
}
else{
while(lef.size() && ants[i].x-lef.front()>2*m){
lef.pop_front();
}
ans+=lef.size();
}
}
cout<<ans<<'\n';
}
$N$개의 공이 있고, $1$번공은 검정색, 나머지는 흰색입니다. $1$이상 $N$이하의 범위에서 Uniform하게 $i$와 $j$를 독립적으로 선택해 두 공을 바꾸는 작업을 $K$번 수행할 때, 최종적으로 검정색 공이 있는 번호의 기댓값을 구하는 문제입니다.
현재 $x$번에 검정색 공이 있다고 가정하면, 한 번의 작업에 대해 케이스를 네 가지로 나눌 수 있습니다.
위의 각각을 살펴보면 1번의 경우 $\frac{(N-1)^2}{N^2}$의 확률로 발생합니다. $y\ne x$에 대해 한 번의 작업 후 검정색 공이 $y$번으로 바뀔 확률은 2, 3번 각각 1가지 경우가 있으므로, $\frac{2}{N^2}$입니다. $x$번에 유지될 확률은 1번 혹은 4번의 경우이므로, $\frac{(N-1)^2+1}{N^2}$입니다.
그런데, $x$번에 유지될 확률을 다르게 해석하여 $\frac{N^2-2N}{N^2}+\frac{2}{N^2}$로 생각하게 되면, 확률/기댓값의 선형성에 따라 다음 두가지 케이스로 해석할 수 있습니다.
만약 $K$번의 작업 중 1번이 한번이라도 일어난다면, 모든 번호에 대해 uniform해지기 때문에, 검정색 공이 최종적으로 $i$번의 위치에 있을 확률은 모든 $i$에 대해 $\frac{1}{N}$으로 동일합니다. 즉, 이 경우 기댓값은 $\frac{N+1}{2}$입니다.
만약 $K$번의 작업 중 단 한번도 1번이 일어나지 않으면, 검정색공은 $1$번에 유지됩니다. 이 경우 기댓값은 $1$입니다.
$K$번의 작업 중 단 한번도 1번이 일어나지 않을 확률은 $(\frac{N^2-2N}{N^2})^K$입니다. 즉, 최종적으로 기댓값은 $1\cdot(\frac{N^2-2N}{N^2})^K+\frac{N+1}{2}\cdot(1-(\frac{N^2-2N}{N^2})^K)$입니다. 모듈로 역원을 $O(\lg P)$에 구할 수 있으므로, $O(\lg P)$에 해결가능합니다. AtCoder에서 제공하는 ACL을 처음으로 사용해본 문제였습니다.
+ 이 문제의 더 쉬운 풀이로 $O(K)$ DP가 있습니다. 제한을 보고 DP인가 했지만 DP보다 위의 풀이가 먼저 생각나 이렇게 풀었습니다. 사실 대회 끝나고 changhw(now_cow)의 코드를 보기 전까지 DP 풀이는 생각나지 않았습니다.
void solve(){
ll i,j,k;
ll n,m;
cin>>n>>m;
ll q=n*n-2*n,p=n*n;
q%=mod,p%=mod;
ll prob=q*inv_mod(p,mod)%mod;
prob=pow_mod(prob,m,mod);
ll rprob=(1-prob+mod)%mod;
ll ans=0;
ans=prob+(1+n)%mod* inv_mod(2,mod)%mod*rprob%mod;
ans%=mod;
cout<<ans<<'\n';
}
두 구간이 \texttt{겹친다}는 것은 두 구간이 abab형태로 exclusive하게 겹친다는 것을 의미합니다. $N$개의 구간이 주어질 때 최대한 많은 구간과 겹치도록 하는 정수 구간을 만드는 문제입니다. 만약 그런 구간이 많다면 $(l,r)$의 사전순으로 가장 작은 구간을 출력합니다.
가능한 답의 두 끝점은 주어진 구간들의 $l+1$ 혹은 $r+1$ 중 있습니다. 따라서 좌표압축을 시킨 뒤 정렬하고, 원하는 범위에 +, - 시키고 원하는 범위의 최댓값을 찾는 세그를 만든 뒤 우선 모든 구간의 범위를 + 시켜두고, 이후 구간을 정렬해 sweeping하면서 빼주고 하면 됩니다. 대회 끝나고 여러번 제출해봤는데, 아직도 왜틀렸는지 모르겠습니다.
풀이는 에디토리얼과 같고, 심지어 맞은 사람의 코드와 코드도 거의 동일한데 구현을 뭔가 lower_bound, upper_bound나 +1 할 때 안할 때 이런 구분을 잘못한 것 같습니다. $O(N\log N)$에 해결가능합니다.
실제 대회 때는 E푼시점에 F 푼사람 10 언더, G 푼사람 20정도여서 G로 바로 갔습니다.
수열이 주어질때, 원하는 수 하나를 바꾸어서 LIS(Longest Increasing Subsequence)의 길이를 최대로 만드는 문제입니다.
수를 하나 바꾸기 때문에 답은 원래 수열의 LIS의 길이 혹은 +1입니다. 그럼 언제 LIS길이 +1이 답이 되는지 살펴보면 다음 경우입니다.
즉, 각각 구해주면 됩니다. 1번 경우는 LIS를 한번씩 더구하면 되고, 2번 경우는 이제 여러 LIS가 있을 수 있는데, 다음과 같이 구하면 됩니다.
위의 두 가지 경우(최소-최소, 최대-최대)를 검사하면 되는데, 간단한 증명은 다음과 같습니다.
LIS만 잘 구하고 역추적만 잘하면 되므로 $O(N\log N)$에 해결가능합니다.
// 대회 당시 짰던 코드가 너무 더러워서 다시 짰습니다.
void solve(){
int i,j,k;
int n;
cin>>n;
vector<int>arr(n);
for(i=0;i<n;i++)cin>>arr[i];
// find LIS with O(nlogn) with smallest sequence
auto lis=[](const vector<int>&arr)->vector<int>{
vector<int>lis;
vector<int>idx;
vector<int>prev(arr.size(),-1);
for(int i=0;i<arr.size();i++){
auto it=lower_bound(all(lis),arr[i]);
if(it==lis.end()){
lis.push_back(arr[i]);
idx.push_back(i);
if(idx.size()>1){
prev[i]=idx[idx.size()-2];
}
}
else{
*it=arr[i];
idx[it-lis.begin()]=i;
if(it!=lis.begin()){
prev[i]=idx[it-lis.begin()-1];
}
}
}
vector<int>ret;
int i=idx.back();
while(i!=-1){
ret.push_back(arr[i]);
i=prev[i];
}
reverse(all(ret));
return ret;
};
auto check1=[&lis](const vector<int>&arr)->int{
vector<int>brr=lis(arr);
if(lis(vector<int>(arr.begin()+1,arr.end())).size()==brr.size()){
return brr.size()+1;
}
else if(lis(vector<int>(arr.begin(),arr.end()-1)).size()==brr.size()){
return brr.size()+1;
}
return 0;
};
auto check2=[&lis](const vector<int>&arr)->int{
vector<int>brr=lis(arr);
vector<int> lis_idx;
int now = 0,i;
for (i = 0; i < brr.size(); i++) {
while (arr[now] != brr[i])now++;
lis_idx.push_back(now);
now++;
}
for (i = 0; i < brr.size() - 1; i++) {
if (brr[i] + 1 == brr[i + 1]) {
continue;
} else if (lis_idx[i] + 1 == lis_idx[i + 1]) {
continue;
} else {
return brr.size() + 1;
}
}
return 0;
};
int ans;
if(n>1&&(ans=check1(arr))){
cout<<ans<<'\n';
}
else if(ans=check2(arr)){
cout<<ans<<'\n';
}
else{
reverse(all(arr));
for(auto&x:arr)x=-x;
if(ans=check2(arr)){
cout<<ans<<'\n';
}
else{
cout<<lis(arr).size()<<'\n';
}
}
}
전날 쳤던 모든 대회/연습이 망해서 걱정했으나 2400퍼폼에 전체 76등이라는 준수한 성적을 거두어 기분이 좋습니다. 두라운드 연속해서 2000+ 퍼폼인 만큼 유지해보도록 해야겠습니다. 아직도 F는 왜틀렸는지 모르겠는데, 혹시 제 코드들 보고 틀린 부분이 있으면 알려주시면 감사하겠습니다.
B번 문제에 문제가 있어(…) 조금 그런 대회 셋이였습니다. 그걸 차치하고 보더라도 개미 문제나 LIS등 웰노운 문제들을 살짝 변형한 문제들이 많아 아쉽습니다. G가 너무 쉬웠고, 그 중간에 확률문제는 DP말고 수학으로 푸는 버전으로 나왔다면(물론 그럼 F 갈 확률이 높지만) 합니다.
]]>지난주 토요일 ABC 358을 망치고 첫 atcoder였습니다. 이번주 월요일 연습때 폼이 나쁘지 않아 걱정반 기대반으로 시작했습니다.
$N$개의 문자열이 주어지고 이 중 Takahashi
의 개수를 세는 문제입니다. 대회 후 이 글을 작성 중에 알았는데, 모든 문자열은 Takahashi
혹은 Aoki
입니다.
항상 그렇듯 빠르게 해석하고 구현하는 것이 관건입니다. count
함수를 사용하여 Takahashi
의 개수를 세었습니다.
void solve(){
int i,j,k;
ll n;
cin>>n;
vector<string>str(n);
for(i=0;i<n;i++)cin>>str[i];
cout<<count(str.begin(), str.end(),"Takahashi");
}
$1$부터 $N$까지 각각 두 개 씩 총 $2N$개의 정수가 주어지고, 이 중 같은 정수 사이에 다른 수가 정확히 한 개 있는 정수의 개수를 구하는 문제입니다.
들어오는 수의 위치 두개를 저장해 $2$가 차이나는 지 확인하면 됩니다. 처음에 $N$개의 정수만 입력받아 로컬에서 RTE가 나와 이를 찾아 수정하느라 3분정도 소요되었습니다.
void solve(){
int i,j,k;
ll n;
cin>>n;
vector<vector<ll>>arr(n+1);
for(i=0;i<n*2;i++){
cin>>j;
arr[j].pb(i);
}
ll cnt=0;
for(i=1;i<=n;i++){
if(arr[i][0]+2==arr[i][1])cnt++;
}
cout<<cnt;
}
아래와 같은 그림에서 출발점과 도착점이 블럭 내부에 주어질 때 최소로 블록을 바꾸어 도착점까지 이동하는 문제입니다.
범위가 $2\times 10^{16}$이라 직접하는 것은 안됩니다.
가로와 세로 중 어디가 더 긴 지 확인하고, 일단 대각선으로 이동하여, 가로가 크다면 마지막에 두 칸 씩, 세로가 크다면 한 칸 씩 이동하는 것이 최적이라는 것을 알 수 있습니다.
void solve(){
int i,j,k;
ll a,b,c,d;
cin>>a>>b>>c>>d;
ll dx=abs(c-a),dy=abs(d-b);
if(dy>=dx){
cout<<dy;
}
else{
ll ans=dy;
dx-=dy;
if(a<c){
if((a+b)%2==1){
ans+=(dx+1)/2;
}
else{
ans+=dx/2;
}
}
else{
if((a+b)%2==0){
ans+=(dx+1)/2;
}
else{
ans+=dx/2;
}
}
cout<<ans;
}
}
+ 대회 후 changhw(now_cow)의 코드를 봤는데 깔끔하게 잘 짜서 첨부합니다
int main() {
fastio;
ll sx, sy, tx, ty;
cin >> sx >> sy >> tx >> ty;
if((sx + sy) % 2 == 0) sx++;
if((tx + ty) % 2 == 0) tx++;
cout << abs(sy - ty) + max(0LL, (abs(sx - tx) - abs(sy - ty) + 1)/2);
return 0;
}
길이 $N$의 A
, B
, ?
로만 이루어진 문자열과 $K$가 주어지고 ?
에 A
혹은 B
를 넣을 때, 길이 $K$의 substring이 palindrome이 되지 않도록 하는 문자열의 개수를 구하는 문제입니다.
$K$의 범위가 $10$이하이고, $N$이 $1000$이하이므로, $N\times 2^K$ DP로 풀 수 있습니다.
i
번째 문자까지 고려하고, 마지막 $K$개의 문자를 A
를 0
, B
를 1
이라 생각할 때의 비트마스크를 j
라고 할 때 조건을 만족하는 경우의 수를 DP[i][j]
로 놓고 구할 수 있습니다.
첫 $K-1$개의 문자에 대해서는 조건을 항상 만족하므로 별도의 처리 없이 진행하고, 이후에는 j
의 비트마스크가 팰린드롬이 아닐 때만 DP[i][j]
를 갱신합니다. 만약 팰린드롬이라면 0을 넣어줍니다. (i,j)
에서 갈 수 있는 상태가 (i+1,x)
라고 하면 x
는 j
의 첫 비트를 지우고, 비트를 하나씩 시프트 시키고, 마지막 비트만 0
혹은 1
로 설정해주면 됩니다. 즉, $N\times 2^K$가지의 상태에 대해 각각 $2$개 씩 전파해주면 됩니다.
미리 팰린드롬을 모두 구해둔다면, $O(N\times 2^K)$로 풀 수 있습니다. 매번 구한다면 $O(NK\times 2^K)$도 시간 내에 들어갈 것 같습니다. 아래 코드에서는 $K$대신 m
을 사용하였습니다.
void solve(){
int i,j,k;
ll n,m;
cin>>n>>m;
string str;
cin>>str;
vector<vector<ll>> dp(n+1,vector<ll>(1<<m,0));
vector<bool> isok(1<<m,true);
for(i=0;i<(1<<m);i++){
vector<int> cnt(m,0);
for(j=0;j<m;j++){
if(i&(1<<j)){
cnt[j]++;
}
}
vector<int> cnt2=cnt;
reverse(all(cnt2));
if(cnt==cnt2)isok[i]=false;
}
dp[0][0]=1;
for(i=1;i<=m-1;i++){
for(j=0;j<(1<<m);j++){
if(str[i-1]!='B'){
ll nex=(j<<1)&((1<<m)-1);
dp[i][nex]+=dp[i-1][j];
dp[i][nex]%=mod;
}
if(str[i-1]!='A'){
ll nex=(j<<1)&((1<<m)-1)|1;
dp[i][nex]+=dp[i-1][j];
dp[i][nex]%=mod;
}
}
}
for(i=m;i<=n;i++){
for(j=0;j<(1<<m);j++){
if(str[i-1]!='B'){
ll nex=(j<<1)&((1<<m)-1);
if(isok[nex]) {
dp[i][nex] += dp[i - 1][j];
dp[i][nex] %= mod;
}
}
if(str[i-1]!='A'){
ll nex=(j<<1)&((1<<m)-1)|1;
if(isok[nex]) {
dp[i][nex] += dp[i - 1][j];
dp[i][nex] %= mod;
}
}
}
}
ll ans=0;
for(i=0;i<(1<<m);i++){
ans+=dp[n][i];
ans%=mod;
}
cout<<ans<<'\n';
}
$N+1$개의 수조가 순서대로 붙어 있고, $i$번과 $i+1$번 사이의 벽의 높이 $H_i$가 주어집니다. 각 수조의 밑면적은 같고, $1$초에 한 번씩 $0$번 수조에 높이 $1$의 물을 채웁니다. 물은 벽을 넘어가면 다음 수조로 흘러 들어갑니다. $1$번 부터 $N$번까지의 각 수조에 물이 언제 처음 넘어 들어가는 지 구하는 문제입니다.
언제 물이 처음으로 넘어가는 지를 예제를 보며 관찰하다 보면 다음 사실을 알 수 있습니다.
이를 통해 물이 걸리는 벽들의 높이는 감소함을 알 수 있습니다. LIS 구할 때와 마찬가지로 스택에 높이가 감소하도록 유지시키며, 넣어주면 됩니다. 각각의 벽에 대해 순차적으로 다음을 해줍니다.
이를 반복하면 $i$번 벽까지 채우는데 걸리는 시간이 마지막에 저장되어 있을 것이므로, 여기에 $+1$해 출력하면 됩니다.
스택에 원소가 들어갈 때, 나갈 때 보여지고, top만 참조하므로 $O(N)$에 풀 수 있습니다.
void solve(){
int i,j,k;
ll n,m;
cin>>n;
vector<ll> arr(n);
for(auto &x:arr)cin>>x;
stack<tlll> st;
for(i=0;i<n;i++){
while(st.size()){
auto [h,idx,sum]=st.top();
if(h>arr[i]){
st.push({arr[i],i,sum+arr[i]*(i-idx)});
break;
}
else{
st.pop();
}
}
if(st.empty()){
st.push({arr[i],i,arr[i]*(i+1)});
}
cout<<get<2>(st.top())+1<<" ";
}
}
$N$개의 양의 정수 $A_1,A_2,\cdots,A_N$이 주어지고, $N$개의 정점을 가진 트리를 만들어야 합니다. $i$번 정점의 차수가 $d_i$라고 할 때, $\sum_{i=1}^{N}d_i^2A_i$의 값을 최소로 하도록 트리를 구성하는 문제입니다.
만약 차수가 모두 정해져 있다고 합시다. 이 때 $\sum_{i=1}^{N}d_i^2A_i$의 값을 최소로 하려면, 재배열 부등식에 의해 $d_i$와 $A_i$는 서로 정렬해 역순으로 매칭시켜야 합니다. 즉, 이는 결국 $A_i$가 작은 것이 최대한 많은 정점에 연결되어 있어야 하며, $A_i$가 큰 것끼리 연결될 필요가 없으므로, $A_i$를 오름차순으로 정렬하여 하나의 트리에 연결해나가는 것이 최적임을 의미합니다.
그렇다면 주어진 $A_i$를 오름차순으로 정렬하고, 해당 순서대로 $1$, $2$, $\cdots$, $N$번 정점으로 재배열 합니다. 이 상황에서 $i$번 정점을 삽입한다고 가정하면, $i$번 정점이 $j$번 정점과 연결된다고 가정할 때, $d_j$가 $1$ 증가하고, $d_i$가 1이 됩니다. 즉, 값은 $((d_j+1)^2-d_j^2)\cdot A_j+A_i=(2d_j+1)\cdot A_j+A_i$만큼 증가합니다.
즉, $(2d_j+1)\cdot A_j$가 가장 작은 정점에 그리디하게 연결해 주는 것이 최적임을 추측할 수 있습니다. 만약 다른 정점에 연결된다면, 더 큰 값을 가져가게 될 것입니다. 이 경우가 이후에 더 이득을 볼 수 있는 지 검사하면 $2d_j+1$은 증가함수이므로 점점 추가되는 값이 커지므로 그럴 수 없습니다.
만약 $(2d_j+1)\cdot A_j=(2d_k+1)\cdot A_k$인 경우가 있고, 이 경우가 최소라고 하더라도 어떤 걸 선택하든 다음 선택에서 다른 것을 선택할 것이므로, 신경쓰지 않아도 됩니다.
이를 우선순위 큐를 사용하여 구현하면 됩니다. 우선순위 큐에는 추가되는 값과 현재 차수를 저장하고, 가장 작은 값을 가진 정점을 빼서 연결해주면 됩니다.
void solve(){
int i,j,k;
ll n,m;
cin>>n;
vector<ll> arr(n);
for(auto &x:arr)cin>>x;
sort(all(arr));
priority_queue<pll,vector<pll>,greater<pll>> pq;
ll ans=0;
pq.push({arr[0],0});
for(i=1;i<n;i++){
auto [val,cnt]=pq.top();
pq.pop();
ll ai=val/((cnt+1)*(cnt+1)-cnt*cnt);
ans+=val+arr[i];
ll nex=ai*((cnt+2)*(cnt+2)-(cnt+1)*(cnt+1));
pq.push({nex,cnt+1});
pq.push({arr[i]*3,1});
}
cout<<ans<<'\n';
}
TODO
F까지는 빠르게 밀었지만, G 풀이를 20분 정도만에 생각하고 구현했지만, 예제가 나오지 않아 디버깅 하는 과정에서 시간이 종료되었습니다. 이후 찾아보니까 ETT+트리압축 혹은 Small to Large를 사용하면 풀 수 있는 문제였습니다. 저는 전자로 생각하고 풀었는데 트리압축이라는 개념을 처음 보았는데, 이런 문제를 풀어봤다면 더욱 빠르게 풀 수 있었을 것 같다는 아쉬움이 듭니다. 최근에 회사를 나와서 백수로 살고 있어서 시간이 많은데, 이 많은 시간을 효율적으로 투자해서 정진해야 할 것 같습니다.
다음주 토요일에는 현대모비스 알고리즘 대회 1차, 종료후 팀연습이 예정되어 있어 ARC를 풀지 못할 것 같지만, 일요일 ABC를 참여해보도록 하겠습니다.
C번을 제외하고는 마음에 드는 셋이였습니다. D번이나 여러 문제에서 예외처리를 해주지 않도록 제한 조건을 주어서 좋았습니다. 평소 ABC보다 약간 쉬운 느낌이 들었습니다.
]]>