$$\newcommand{\Z}{\mathbb{Z}} \newcommand{\R}{\mathbb{R}} \newcommand{\Q}{\mathbb{Q}} \newcommand{\N}{\mathbb{N}}\newcommand{\C}{\mathbb{C}} \newcommand{\oiv}[1]{\left] #1 \right[} \newcommand{\civ}[1]{\left[ #1 \right]} \newcommand{\ad}[1]{\text{ad}(#1)} \newcommand{\acc}[1]{\text{acc}(#1)} \newcommand{\Setcond}[2]{ \left\{\, #1 \mid #2 \, \right\}} \newcommand{\Set}[1]{ \left\{ #1 \right\}} \newcommand{\abs}[1]{ \left\lvert #1 \right\rvert}\newcommand{\norm}[1]{ \left\| #1 \right\|}\newcommand{\prt}{\mathcal{P}}\newcommand{\st}{\text{ such that }}\newcommand{\for}{\text{ for }} \newcommand{\cl}[1]{\text{cl}(#1)}\newcommand{\oiv}[1]{\left] #1 \right[}\newcommand{\interior}[1]{\text{int}(#1)}$$

Little Piplup 7월 22일 팀연습::::Gratus' Blog

Little Piplup 7월 22일 팀연습

알고리즘 문제풀이/Team Little Piplup 2019. 7. 25. 00:55

UCPC 전 마지막일지 한번 더 할지 사실 잘 모르겠지만, 아무튼 일곱번째 팀연습.

사실 오늘 Div.3에서 CDE 정도만 풀어보려고 했지만, C번부터 막 2차원에서 돌아다니는 문제가 나왔는데 너무 읽기 싫어 보이므로 그냥 패스하고 연습 리뷰를 하기로 했다.

3PC로 진행했고, 시간을 맞춰 팀연습을 돌기로 한 I HATE PS 팀과 같이 동시에 버추얼을 돌았다.

:god:

사실 퍼플 2명에, 구현력 우리보다 훨씬 좋은 멤버가 들어간 아헤피팀이랑 솔브 수가 같다는건 꽤 고무적이다.

우리가 레이팅에 비해 시너지가 좋은 팀인게 첫번째고, 두번째는 셋이 우리한테 나쁘지 않은 문제들이 꽤 많았다. 세번째는 G번 때문인듯.

특히 D번이나 K번같이 프로그래밍 필요없고 수학만 풀면 되는 문제가 그렇고..

L번의 경우, 왜 맞았는지 전혀 모르겠기 때문에 (...) 뇌피셜 + 믿음메타!

문제번호 A B C D E J K L
레이팅 1700 1800 1900 1900 2000 2200 2300 2400
AC 시간 00:15 00:24 00:59 (+1) 01:21 01:11 02:55 (+2) 03:58 (+3) 03:48

크게는 초반 5문제 / 후반 3문제.


A. Alarm Clock (Round #451 Div2 D)

Solve : Coffeetea, Diordhd

Code : Coffeetea

알람을 듣고 일어나는 조건 ($k$개의 알람 시계가 $m$분 이상 울릴 때) 이 주어지고, 알람 시계를 몇 개 꺼서 계속 자려고 할 때 (...목표가 자는 거면 알람을 왜 맞춘 거지;;) 

꺼야 하는 시계의 개수를 세는 문제. 

그리디하게 잘 세면 된다. 별로 어려운 문제는 아니기도 하고, 세명이 다 읽을 필요는 없을 것 같기도 하고, 딱봐도 나보다 다른 2명이 더 잘 풀게 생긴 유형이라 던지고 나는 B번을 읽으러 갔다.

...더보기
#include<bits/stdc++.h>
using namespace std;
#define day 1000000
 
bool alarm[1000005];
 
int main(){
    int n, m, k;
    scanf("%d %d %d", &n, &m, &k);
    for ( int i = 0 ; i < n ; ++i ) {
        int temp;
        scanf("%d", &temp);
        alarm[temp] = true;
    }
    int ret = 0;
    int current = 0;
    int ptr1 = 0, ptr2 = 0;
    while(ptr1 <= day ) {
        ++ptr1;
        if ( ptr1 - ptr2 > m ) ++ptr2;
        if ( alarm[ptr1] ) ++current;
        if ( alarm[ptr2] ) --current;
        if ( current >= k ) {
            alarm[ptr1] = false;
            --current;
            ++ret;
        }
    }
    printf("%d", ret);
}

B. Palindromic Cut (2017-2018 NEERC Southern Subreg H)

Solve : Gratus907

Code : Gratus907

NEERC인거 알고 방금 백준 공짜 1문제 먹으려고 가봤는데 Southern Subreg는 올라와있지 않다 (:disappointed:)

문자열을 재배열한 다음, 최대한 길게 서로 같은 길이의 Palindrome 들로 잘라내려고 한다.

홀수개 들어온 문자랑 짝수개 들어온 문자를 구분한 다음, 홀수개 들어온 것들은 팰린드롬 가운데에 넣고, 짝수개는 앞뒤로 하나씩 넣겠다는 식으로 생각해 주면 된다. 혹시 홀수개 들어온 문자가 부족할 때는, 짝수개 들어온 문자들을 이용해서 (하나씩 떼어서) 쓰면 된다.

별로 어렵지는 않은데, A번 버리고 와서 잡은게 String문제라니..흠;;

...더보기
#include <bits/stdc++.h>
#pragma GCC optimize("O3")
#pragma GCC optimize("Ofast")
#define ll long long
#define all(x) ((x).begin()),((x).end())
#define usecppio ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
using namespace std;
 
typedef pair <int, int> pii;
 
int cnt[150];
char s[401010];
char ans[401010];
vector <char> even;
vector <char> odd;
int odds, evens;
int main()
{
    int n;
    scanf("%d", &n);
    scanf("%s", s);
    for (int i = 0; i < n; i++)
        cnt[s[i]]++;
    for (int i = 0; i < 150; i++)
    {
        if (cnt[i])
        {
            if (cnt[i] % 2)
            {
                cnt[i]--;
                odd.push_back(i);
                odds++;
            }
            while (cnt[i])
            {
                cnt[i] --;
                cnt[i] --;
                even.push_back(i);
                evens++;
            }
        }
    }
    if (odd.empty())
    {
        printf("1\n");
        for (int i = 0; i<n/2; i++)
        {
            ans[i] = even[i];
            ans[n-1-i] = even[i];
        }
        ans[n] = '\0';
        printf("%s\n", ans);
    }
    else
    {
        while (even.size() % odd.size())
        {
            odd.push_back(even.back());
            odd.push_back(even.back());
            odds+=2;
            evens--;
            even.pop_back();
        }
        int l = n / odds;
        printf("%d\n", odds);
        while(!odd.empty())
        {
            ans[l/2] = odd.back();
            odd.pop_back();
            for (int i = 0; i<l/2; i++)
            {
                ans[i] = even.back();
                ans[l-1-i] = even.back();
                even.pop_back();
            }
            ans[l] = 0;
            printf("%s ", ans);
        }
    }
}

C. Minimum and Maximum (2016-2017 NEERC Southern Subreg B)

Solve : Gratus907, Diordhd

Code : Gratus907

Coffeetea가 그래프인 E번을 읽는 동안 둘이 같이 생각해본 문제.

최대 $\lceil\frac{3n}{2}\rceil - 2$ 번의 비교 연산을 써서, $n$개짜리 배열의 최소와 최대 원소가 들어있는 인덱스를 찾아야 한다.

 

처음에 생각한 솔루션은 토너먼트 식으로, 1번과 2번, 3번과 4번...을 비교해서 "큰 쪽" 과 "작은 쪽" 을 나누고... 이런 식으로 한 다음, 다시 그 안에서 반복해서 맨 위와 아래를 찾는 방법인데,

구현하다가 사실 필요한 연산을 정확히 다 쓰면, 저렇게 한 번 잘라 낸 다음 ($\frac{n}{2}$ 번 비교 연산을 쓰면 된다)

'큰 것' 들 중 가장 앞 원소와 나머지 모두를 하나씩 비교하고 (더 큰 걸 찾으면 가장 앞 원소를 버리고...)

'작은 것'들도 똑같이 하면 정확히 필요한 연산횟수를 다 써서 풀 수 있다는 걸 알았다.

 

Interactive 너무싫다 ㅠㅠ $\texttt{fflush(stdout)}$ 한번 안해서 1틀.

...더보기
#include <bits/stdc++.h>
#pragma GCC optimize("O3")
#pragma GCC optimize("Ofast")
#define ll long long
#define all(x) ((x).begin()),((x).end())
#define usecppio ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
using namespace std;
 
typedef pair <int, int> pii;
 
char reply[10];
int mini[50];
int maxi[50];
int main()
{
    int t;
    scanf("%d", &t);
    while(t--)
    {
        memset(mini,0,sizeof(mini));
        memset(maxi,0,sizeof(maxi));
        int n;
        scanf("%d",&n);
        for (int i = 1; i<n; i+=2)
        {
            printf("? %d %d\n",i,i+1);
            fflush(stdout);
            scanf(" %s",reply);
            if (reply[0] == '<')
            {
                mini[(i+1)/2] = i;
                maxi[(i+1)/2] = i+1;
            }
            else
            {
                mini[(i+1)/2] = i+1;
                maxi[(i+1)/2] = i;
            }
        }
        if (n%2)
        {
            mini[(n+1)/2] = n;
            maxi[(n+1)/2] = n;
        }
        int tmin = mini[1], tmax = maxi[1];
        for (int i = 2; i<=(n+1)/2; i++)
        {
            printf("? %d %d\n",tmin,mini[i]);
            fflush(stdout);
            scanf("%s", reply);
            if (reply[0] == '>')
                tmin = mini[i];
        }
        for (int i = 2; i<=(n+1)/2; i++)
        {
            printf("? %d %d\n",tmax,maxi[i]);
            fflush(stdout);
            scanf("%s", reply);
            if (reply[0] == '<')
                tmax = maxi[i];
        }
        printf("! %d %d\n",tmin,tmax);
        fflush(stdout);
    }
}

 


E. Orientation of Edges (2017-2018 NEERC Southern Subreg G)

Solve : Coffeetea

Code : Coffeetea

어떤 그래프가 주어지는데, 방향이 주어진 것도 있고 아닌 것도 있다.

방향이 주어지지 않은 간선들에 대해서, 방향을 줘서 $s$ 애서 도달할 수 있는 정점의 수를 최대화 / 최소화 하는 방법을 찾는 문제.

DFS를 돌면서 그리디하게 간선들을 골라내면 되는데 (즉, "지금 저 간선이 방향이 이렇게 되면 갈 수 있는데" 라는 생각이 들면 그렇게 하면 된다) Coffeetea가 이런 직관이 좋은 편이라 그런지 2천짜리 그래프 문제를 생각보다 빠르게 풀어냈다.

...더보기
#include<bits/stdc++.h>
using namespace std;
 
struct Road{
    int type, start, end;
};
 
vector<int> going[300005];
vector<int> going2[300005];
bool done[300005];
bool done2[300005];
 
int main(){
    int n, e, s;
    scanf("%d %d %d", &n, &e, &s);
    Road road[e];
    Road road2[e];
    for ( int i = 0 ; i < e ; ++i ) {
        int a, b, c;
        scanf("%d %d %d", &a, &b, &c);
        road[i].type = road2[i].type = a;       //1은 방향, 2는 무방향
        road[i].start = road2[i].start = b;
        road[i].end = road2[i].end = c;
        if ( a == 1 ) {
            going[b].push_back(i);
            going2[b].push_back(i);
        } else {
            going[b].push_back(i);
            going2[b].push_back(i);
            going[c].push_back(i);
            going2[c].push_back(i);
        }
    }
 
    int visited = 1;
    done[s] = true;
    queue<int> stk;
    stk.push(s);                        //스택에 집어넣을 때마다 +1하자
 
    while(!stk.empty()) {
        int current = stk.front();
        stk.pop();
 
        int size = going[current].size();
        for ( int i = 0 ; i < size ; ++i ) {
            int temp = going[current][i];       //거리 번호
 
            if ( road[temp].type == 1 || road[temp].type == 3) {           //나가는 방향 그래프이다
 
                if ( !done[ road[temp].end ]) {
                    done[ road[temp].end ] = true;
                    stk.push(road[temp].end);
                    ++visited;
                }
 
            } else if ( road[temp].type == 2 ) {            //양방향 그래프이다
                
                if ( road[temp].start == current ) {
                    road[temp].type = 3;
                    if ( !done[ road[temp].end ]) {
                        done[ road[temp].end ] = true;
                        stk.push(road[temp].end);
                        ++visited;
                    }
                } else {
                    road[temp].type = 4;
                    if ( !done[ road[temp].start ]) {
                        done[ road[temp].start ] = true;
                        stk.push(road[temp].start);
                        ++visited;
                    }
                }
 
            } else if ( road[temp].type == 4 ) {           //들어오는 방향 그래프이다
 
            }
        }
    }
    printf("%d\n", visited);
    for ( int i = 0 ; i < e ; ++i ) {
        if ( road[i].type == 3 || road[i].type == 2) printf("+");
        else if ( road[i].type == 4 ) printf("-");
    }
 
 
 
    int visited2 = 1;
    done2[s] = true;
    queue<int> stk2;
    stk2.push(s);                        //스택에 집어넣을 때마다 +1하자
 
    while(!stk2.empty()) {
        int current = stk2.front();
        stk2.pop();
 
        int size = going2[current].size();
        for ( int i = 0 ; i < size ; ++i ) {
            int temp = going2[current][i];       //거리 번호
 
            if ( road2[temp].type == 1 || road2[temp].type == 3) {           //나가는 방향 그래프이다
 
                if ( !done2[ road2[temp].end ]) {
                    done2[ road2[temp].end ] = true;
                    stk2.push(road2[temp].end);
                    ++visited2;
                }
 
            } else if ( road2[temp].type == 2 ) {            //양방향 그래프이다
                
                if ( road2[temp].start == current ) {
                    road2[temp].type = 4;
                } else {
                    road2[temp].type = 3;
                }
 
            } else if ( road2[temp].type == 4 ) {           //들어오는 방향 그래프이다
 
            }
        }
    }
    printf("\n%d\n", visited2);
    for ( int i = 0 ; i < e ; ++i ) {
        if ( road2[i].type == 3 || road2[i].type == 2) printf("+");
        else if ( road2[i].type == 4 ) printf("-");
    }
 
}

D. As Fast as Possible (Round 364 Div1 A)

Solve : Gratus907, Diordhd

Code : 코딩 비중이 없으므로 의미가 없다. 내가 파이썬으로 짜긴 했다.

코포에 가끔 나오는, 

일단 Binary Search로도 답 구할 수 있으니 코딩 문제라고 주장하지만 수학으로 $\mathcal{O}(1)$ 에 풀어도 되는 문제.

 

$n$ 명의 사람이 $l$ 미터를 가야 하는데, 사람은 1분에 $v_1 m$ 를 갈 수 있고, 한 대 밖에 없는 버스는 $v_2 m$ 를 갈 수 있다.

버스에 한번에 $k$명을 태울 수 있을 때, 모든 사람이 가장 빠르게 도착할 수 있는 시간은?

 

우선, 모든 사람의 속력이 같기 때문에, 어차피 가장 늦게 도착하는 사람이 기준이 된다는 사실을 알 수 있다. 그리고, $v_2 > v_1$ 이 보장되므로, 버스를 잠깐이라도 탈 수 있으면 타야 하고, 버스를 타면 무조건 앞서나가기 때문에 버스가 왔다갔다하면서 사람을 적당히 실어날라서 1인당 버스 사용 시간을 모두 똑같이 $b$ 분으로 맞춰 주는 게 항상 최적임을 직관적으로 파악할 수 있다.

즉, 모든 사람이 동시에 도착하는 시간 $T$분에 도착하기 위해, 1인당 $a$분만큼 걷고 $b$분 만큼 버스를 탄다고 하자. 또한, $n$ 명은 사실 중요하지 않고 버스가 몇 번 다녀야 하는지가 중요하니까, 앞으로는 $k$를 무시하는 대신 $n$ 을 $\lceil \frac{n}{k} \rceil$ 로 바꿔 주자. 

다음 식을 얻을 수 있다.

$$av_1 + bv_2 = l$$

$$a + b = T$$

또한, 버스를 1인당 $b$분 타려면, 버스가 얼마를 움직여야 하는지 생각해 보자. 돌아와서 사람을 태워야 하는데, 돌아올 때는 $v_1 + v_2$ 속도로 가까워 지므로, 버스가 앞으로 $n$번 운행한다면 $\frac{b(v_2-v_1)}{(v_2+v_1)}$ 시간만큼 $n-1$ 번 뒤로 와야 한다.

즉, 버스의 총 운행 시간은 $T = (n-1)\frac{b(v_2-v_1)}{(v_2+v_1)} + nb$ 이다.

식 3개에 문자 3개 ($a,b,T$) 이므로, 열심히 연립방정식을 풀면 된다. (실제로 풀 때는 버스가 이동해야 하는 거리를 기준으로 했다)

...더보기
import math
n, l, v1, v2, k = map(int,input().split())
n = math.ceil(n/k)
mm = (n+((v2-v1)*(n-1)/(v1+v2)))
T = (l)/(v1-((v1-v2)/mm))
print(T)

이시점부터 F 번을 코딩하기 시작해서, 이 F번의 코딩은 2시간 40분동안 끝을 못 냈고 결국 분노의 15틀로 버추얼이 끝났다. Binary Search를 이용한다는 개념도 OK, 대충 다 맞게 한거 같은데, 뭐가 문제인지 사실 잘 모르겠다.

 

대략 이때부터는 

팀내에서 비교적 복잡한 구현을 침착하게 하는 데 강한 (여전히 팀 전체의 특성은 어쩔수가 없어서, 비슷한 레이팅의 다른 사람들에 비하면 Solve에 더 강하지만) Coffeetea가 나랑 같이 수학문제를 풀고 있고, 

팀내에서 수학에 강한 Diordhd가 복잡한 구현에 말려버렸다. 팀 전략상으로는 굉장히 문제가 많았는데, 다행히 뒷 문제들을 간신히 3개 풀었다.


J. Nature Reserve (Round 514 Div2 D)

Solve : Gratus907

Code : Gratus907

대략적으로, 세세한 조건들을 제끼고 러프하게 말하자면 

$ y = R$ 위에 중심을 둔 반지름 $R$ 짜리 원을 그려서, 주어진 $n$개의 점을 모두 커버할 수 있는 최소의 $R$을 찾아라

 

이런 문제를 풀면 된다.

$R$에 대해 이분 탐색을 하는데, 한 번 탐색할 때 $n$개의 점 각각에서 $R$짜리 원을 그려서 $y = R$ 과 만나는 segment의 left 와 right 점을 들고 다니면, 위 조건을 만족하는 중심이 존재하는지를 $\mathcal{O}(n)$ 에 판정할 수 있다. $n = 10^5 $ 이므로, 대충 150번 쯤 이분 탐색 돌면 만족할 만한 정확도를 얻으면서도 시간 초과를 받지 않을 수 있다. (826ms였는데, 솔직히 1000ms였으면 약간 쫄릴뻔 했다. 이럴때 잘못하면 서로 다른 반복값을 갖는 코드를 제출해 보면서 기도해야 하는 상황이 나오기 때문에..)

...더보기
#include <bits/stdc++.h>
#pragma GCC optimize("O3")
#pragma GCC optimize("Ofast")
#define ll long long
#define all(x) ((x).begin()),((x).end())
#define usecppio ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
using namespace std;
#define eps 1e-7
typedef pair <int, int> pii;
typedef pair <long double, long double> pdd;
int n;
struct point
{
    long double x, y;
    point(long double x, long double y)
    {
        this->x = x;
        this->y = y;
    }
    point()
    {
        point(0,0);
    }
};
int a[10];
point animal[101010];
 
bool is_valid(long double R)
{
    long double lo = -1e18;
    long double hi = 1e18;
    for (int i = 0; i<n; i++)
    {
        if(2*R<animal[i].y)
            return 0;
        long double len = sqrt(R-(R-animal[i].y))*sqrt(R+(R-animal[i].y));
        lo=max(lo,animal[i].x-len);
        hi=min(hi,animal[i].x+len);
        if (lo>hi)
            return 0;
    }
    return (hi >= lo);
}
int main()
{
    usecppio
    cin >> n;
    bool neg = 0, pos = 0;
    long double lo = 0, hi = 1e18;
    for (int i = 0; i<n; i++)
    {
        long double x, y;
        cin >> x >> y;
        neg |= (y<0);
        pos |= (y>0);
        y = abs(y);
        lo = max(lo, y/2);
        animal[i] = point(x,y);
    }
    if (neg&pos)
    {
        cout << -1;
        return 0;
    }
    else
    {
        int iter = 0;
        while(true)
        {
            iter++;
            long double mid = (lo+hi)/2;
            if (abs(hi-lo)<eps)
                break;
            if (iter == 150)
                break;
            if (is_valid(mid))
                hi = mid;
            else
                lo = mid;
        }
        cout << fixed << setprecision(10) <<(lo+hi)/2;
    }
}

L. Graph Cutting (Round 238 Div2 E)

Solve : Gratus907

Code : Gratus907

Coffeetea 가 K번을 생각하는 동안, 내가 L번을 풀었다. 사실 풀었다고 하긴 좀 그런게, 그냥 맞을 것이라고 믿고 적당히 코딩하고 기도하자 뭐 이런 느낌이라서...

그래프가 주어지는데, 이 그래프를 다음과 같이 Edge를 2개씩 묶을 수 있다.

$u$ 에서 $v$로, $v$에서 $w$ 로 Edge가 있으면, 이것들을 $(u,v,w)$ 엣지로 묶는다.

모든 Edge를 다 묶을 수 있을까?

 

짝수개의 Edge를 가진 연결 그래프면 다 묶을 수 있지 않겠느냐는 근본없는 생각을 일단 믿은 다음, 

아무 점에서나 시작해도 되지 않을까 라는것도 일단 믿고.

적당히 인접한 점으로 가는 간선들을 보면서 v자 형태로 잘 묶어 주면 되겠지 라고 믿고 코딩을 그대로 했다.

왜 맞는지 사실 잘 모르기 때문에 패스

언젠가 생각이 나거나 하면 다시 포스팅 하기로....


K. Lengthening Sticks (Round 317 Div1 A)

Solve : Coffeetea, Diordhd, Gratus907

Code : Gratus907

시간이 10여분 남았을때 나랑 Coffeetea가 각자 식대로 (Coffeetea가 찾은 식을 서로 정리하는 방법이 약간 달랐다. 나는 F번을 코딩하다 빡쳐서 이쪽 보러 온 Diordhd가 정리해준 식을 보고 생각하기도 했고.) 코딩을 해서 제출해보기로 하고 내가 맞았다. 무려 3:58에..!

길이가 $a, b, c$인 세 개의 막대를 가지고 있는데, $l$ 이 주어지는데, 이 $l$을 적당히 ($l$보다 작아도 된다) 세 막대에 나누어 줘서, 삼각형이 성립하게 만들어야 한다. 

 

우선, 그냥은 생각하기 어려우므로 여사건을 생각해 보자. 즉, 먼저 $l$ 이하의 길이를 적당히 나누어 주는 방법 ($l$ 을 4개로 나누는 Partition을 세도 되지만, 그냥 반복문으로 세도 문제는 없다) 의 개수를 센 다음, 그중에 삼각형을 만들지 못하는 경우를 빼면 된다.

먼저, 반복문으로 전체 경우의 수를 세기 위해서는, $l_a + l_b + l_c + t = l$ 을 만족하는 순서쌍 $(l_a , l_b , l_c, t)$의 개수를 세는 것이므로, $i = (l-t)$ 를 0부터 $l$ 까지 올리면서, 각 막대에 나눠줄 길이만 경우의 수로 세면 된다. 자명하게, $i$ 만큼을 3개의 막대에 흩는 것이므로, 이는 다시 합이 $i$ 보다 작거나 같은 수 2개를 고르는 문제로 환원되며, 그 경우의 수는 $\frac{(i+1)(i+2)}{2}$ 개이다.

 

그 다음, $a + l_a$ 가 가장 긴 막대가 된다고 가정하자. 이때 만들 수 있는 삼각형의 개수를 세려면, 

$a + l_a < b + c + l_b + l_c$ 이고, $l_a + l_b + l_c = i$ 인 순서쌍의 개수를 $i$에 따라 세서 더하면 된다. 그러나 우리는 반대로 삼각형이 성립하지 않는 경우를 세고자 하는 것이므로, $a$에 더하면 삼각형이 성립하지 않는 $l_a$ 의 최솟값을 생각해야 한다.

잠시 생각해 보면 이 최솟값을 $k$ 라고 할 때, $k+a = b + c + i - k$ 를 만족하게 하는 $k$가 정답이다. 따라서, $k$ 보다 크거나 같은 수를 $a$에 더하고, 남은 양을 $b$와 $c$에 더하는 문제, 다시 계산하기 쉽게 바꾸면 $i - k = x$ 라고 할 때 합이 $x$ 보다 작거나 같은 두 수의 순서쌍의 개수를 찾으면 되므로 $\frac{(x+1)(x+2)}{2}$ 개이다.

 

앞서 $a$쪽을 가장 긴 막대로 가정했으므로 각 막대에 대해 한번씩 세번 해주면 된다.

...더보기
#include <bits/stdc++.h>
#pragma GCC optimize("O3")
#define int long long
#pragma GCC optimize("Ofast")
#define ll long long
#define all(x) ((x).begin()),((x).end())
#define usecppio ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
using namespace std;
#define eps 1e-7
typedef pair <int, int> pii;
typedef pair <long double, long double> pdd;
 
int solve(int a, int b, int c, int s)
{
    if (s + a < b + c)
        return 0;
    int x = min(s, (s + a - b - c) / 2);
    int ans = (x + 1) * (x + 2) / 2;
    return ans;
}
 
int32_t main()
{
    int a,b,c,l;
    cin >> a >> b >> c >> l;
    int ans = 0;
    for (int i = 0; i <= l; i++)
        ans += ((i + 1) * (i + 2) / 2);
    for (int i = 0; i <= l; i++)
        ans -= solve(a, b, c, i);
    for (int i = 0; i <= l; i++)
        ans -= solve(b, c, a, i);
    for (int i = 0; i <= l; i++)
        ans -= solve(c, a, b, i);
    cout << ans;
}

후기

F번을 Coffeetea가 구현하거나 최소한 구현에 대해 더 많이 생각한 다음 코딩을 시작하고, K번을 같이 빠르게 수식을 찾고 밀어버렸으면 어쩌면 한 3:30쯤에 9개를 풀 수 있었을 것 같은 생각이 든다. 사실 K번을 못 풀었으면 그런 점들이 더 아쉽게 느껴졌을것 같지만, 어쨌든 솔브로는 1개 손해인 셈이기도 하고 왜맞았는지 모를 L번을 하나 맞았으니 뭐...

 

실제로는 3PC일때는 1명이 말려도 어떻게든 라운드가 굴러가지만, 1PC일때는 구현하는 사람이 말려버리면 돌이킬 수 없는 시간 손해를 입기 때문에 구현할때는 느리더라도 침착하게 천천히 생각하는게 필요한 것 같다. 특히 손으로 디버깅하는것도 조금더 익숙해져야 할 것 같고....

 

나름 고무적인건 팀연습때처럼 시간이 좀 많고 같이 생각을 할 수 있는 환경에서는 우리가 의외로 2200 정도 문제까지도 손을 대볼 수 있다는 점. 특히 같이 생각하는게 워낙 효과가 큰 것 같다.

admin