레이블이 수학인 게시물을 표시합니다. 모든 게시물 표시
레이블이 수학인 게시물을 표시합니다. 모든 게시물 표시

2016년 11월 1일 화요일

2022 - 사다리

https://www.acmicpc.net/problem/2022
https://uva.onlinejudge.org/...&problem=1507


\(a, b, c\) 가 주어지면 \(k\) 를 구해내는 문제이다.

피타고라스로 A, B를 구해서 기울기를 구하고, 두 직선의 교점 방정식을 이용했다. 그리고 교점의 y 위치가 \(c\) 가 되는 순간을 구하도록 이분 탐색을 했다.

우선 a가 포함된 직선을 g(x), b가 포함된 직선을 f(x)라 한다면 아래와 같은 정보가 나온다.

\(
\begin{cases}
A = \sqrt{a^2 - k^2} \\
B = \sqrt{b^2 - k^2}
\end{cases}
\)

\(
\begin{cases}
f(x) = \frac{B}{k}x \\
g(x) = -\frac{A}{k}x+A
\end{cases}
\)

두 직선이 만나는 순간을 \( (c_0, c) \)라 한다면 \( f(c_0) = g(c_0) = c \) 이여야 한다.

\(
\begin{align}
f(c_0) = & \frac{B}{k}c_0 = c \\
& c_0 = \frac{k \cdot c}{B}
\end{align}
\)

\(g(c_0) = g(\frac{k \cdot c}{B}) = c\) 가 나온다면 정답일것이다.

k를 찾는 과정에서 \(f(c_0) \gt c\) 라면 높이가 더 높은 좌표이므로 \(c_0\)을 줄여야한다는 뜻이다. k를 줄이면 \(c_0\)도 줄어든다.

k를 조정해서 만들어진 적당한 x로 f(x) = g(x)가 성립하는 지 확인하면서, 결과에 따라 k를 다시 조정하면 정답이 나온다.

k를 찾는 구간을 줄일 때 epsilon을 1e-5로 설정했을 때는 오답이었다. 이분탐색이 중간에 잘못되고 있나 생각에 구글링하다가 더 작은 결과까지 해보도록 1e-9 로 바꿨더니 정답을 맞았다. 너무 허무함

#include <bits/stdc++.h>
using namespace std;

#define INF 987654321

double a, b, c;

double g(double x, double k){
    double A = sqrt(a*a - k*k);
    return A - (A * x / k);
}

double f(double x, double k){
    return sqrt(b*b - k*k) * x / k;
}

int main(){
    while(~scanf("%lf %lf %lf", &a, &b, &c)){
        double l=0, r=min(a, b);
        
        while(r - l > 1e-9){
            double k = (l+r)/2.0;
            double c0 = k * c / sqrt(b*b - k*k);
            if(g(c0, k) > c){
                l = k;
            } else {
                r = k;
            }
        }
        
        printf("%.3lf\n", l);
    }
    return 0;
}

2016년 5월 30일 월요일

제곱근 구하는 알고리즘 비교

Best Square Root Method - Algorithm - Function (Precision VS Speed)

sqrt를 구하는 14개의 알고리즘을 비교한 글

sqrt(n)을 O($\sqrt{n}$)보다 빠르게 구할 수 없나를 알아보다 찾은 글

답은 못 얻었다.

결론은 std::sqrt 쓰는 게 빠르다.

2016년 5월 11일 수요일

1089 - 엘리베이터

https://www.acmicpc.net/problem/1089

2082번: 시계같은 구현문제일 줄 알고 가벼운 마음으로 시작했다.
풀다보니 모든 수의 합을 구하는 과정에서 DP로 작성하고 있었다. (하지만 DP로는 풀지 못했다.)

우선 각 자리에서 나올 수 있는 숫자들을 추려냈고, (이 과정은 2082번 풀이를 참고)
만들어진 수에 이전에 나온 경우의 수만큼 곱한다. 그만큼 중복으로 등장하기 때문이다.

각 자리에서 나올 수 있는 수를 괄호로 묶었을 때, $(0, 8)$, $(8, 9)$ (예제)와 같은 경우는 1의 자리에서 $8, 9$가 두번씩 나온다. 자리수가 길수록, 다른 자리에 가능한 수가 다양할수록 등장 횟수는 많아질 것이다. 그래서 DP를 생각했다.
가능한 숫자들의 합을 구하는 DP 코드는 아래와 같다.

typedef long long lld;

int n;
vector<int> cand[10]; // i번째 자리에 가능한 숫자들
int nCase[11];
lld DEC[11]={1,1,10,100,1e4,1e5,1e6,1e7,1e8,1e9};

lld dp[11];
lld sum(int pos){
    if(pos >= n) return 0;
    
    lld& r = dp[pos];
    if(r != -1) return r;
    
    r = 0;
    for(int i=0; i<cand[pos].size(); ++i){
        r += nCase[pos+1] * cand[pos][i] * DEC[n-pos] + sum(pos+1);
    }
    return r;
}

뭔가 찝찝한 느낌이 있었는데 채점 40% 정도에서 틀렸다. 오버플로우가 아닐까 싶었다.

아래 입력은 각 자리마다 $(2, 8), (0, 2, 8), (3, 8, 9)$ 의 조합으로 수가 만들어질 수 있다.
3
###.###.###
..#...#...#
.#......##.
#...#.....#
###.###....
위 입력의 정답은 $540.0$ 이다.

가능한 수를 모두 적어보면 아래와 같다.
203
208
209
223
228
229
283
288
289
803
808
809
823
828
829
883
888
889

1의 자리에서 (3+8+9)가 6번 나온다. 10의 자리에서 (0+20+80)이 2번*3번 나온다.
이런식으로 수를 쪼개보면 $10^x$의 자리에서 나올 수 있는 수들의 합은 (자리에서 가능한 숫자들의 합) $\ast$ (전체 경우의 수 / 가능한 숫자의 개수) 이다.

지금 이걸 하려는 이유가 합을 모두 누적한다면 오버플로우가 나기 때문이라고 생각해서이다.
평균값은 (전체의 합)$/$개수이므로 어떤 수 하나의 비율은 (수 하나)$/$개수 이다. 각 숫자들의 비율을 모두 더해도 평균은 똑같이 구할 수 있다!

비율로 계산한다면 (자리에서 가능한 숫자들의 합) $\ast$ (전체 경우의 수 / 가능한 숫자의 개수) / (전체 경우의 수) 를 누적하면 되는 데 약분하면, (자리에서 가능한 숫자들의 합) / (가능한 숫자의 개수) 이다.

풀고 난 후

long long으로 해도 오버플로우는 안 난다.
lld DEC[11]={1,1,10,100,1e3,1e4,1e5,1e6,1e7,1e8,1e9};
배열에서 1e3를 빼먹어서 틀린거였다..

2016년 4월 22일 금요일

11340 - Making the Grade?

https://www.acmicpc.net/problem/11340

$x$를 기말 점수, 나머지를 $a, b, c$라 하면 다음을 만족하는 $x$ $$ 40x \ge \frac{9000}{15a+20b+25c} $$
#include <bits/stdc++.h>
using namespace std;

int main(){
    int T;
    scanf("%d", &T);
    for(int i=0; i<T; ++i){
        int a, b, c;
        scanf("%d %d %d", &a, &b, &c);
        int r = ceil((1800-3*a-4*b-5*c)/8.);
        if( r > 100 ) puts("impossible");
        else printf("%d\n", r);
    }
    return 0;
}

2016년 4월 11일 월요일

10888 - 두 섬간의 이동

https://www.acmicpc.net/problem/10888

다리를 N-1개 지을때마다 그 때까지의 정부가 원하는 2개의 값 현황을 알려줘야한다. 정부가 원하는 값 2개는

  • 두 섬 간에 왕래가 가능한 섬들 (i,j) (i<j) 쌍들의 개수
  • 두 섬 (i,j)가 왕래 가능할 때 섬 i에서 섬 j까지 가기 위해 이용해야 하는 최소 다리 개수의 합

와 같다. 원하는 값을 설명하기 너무 기니까 왕래 가능한 쌍들의 개수를 pairs, 섬 들이 서로 이동하는데 필요한 최소 다리 개수의 합을 bridges라고 하겠다.

이 문제 역시 서로소 집합(Disjoint Set)으로 해결할 수 있다. pairs는 서로 연결된 섬들의 개수를 통해 알 수 있다.
서로 연결된 섬들의 개수를 $x$라 하면 $x=4$일 때 (1, 2) (1, 3) (1, 4) (2, 3) (2, 4) (3, 4) 로 6개이다.
i번째에서 i<j인 개수만큼 더하므로 $$ pairs = \sum_{k=1}^x k$$ 로 나타낼 수 있다.

그럼 bridges는 이러한 pairs개 만큼의 쌍들이 x-1개, x-2개, ..., 1개만큼 떨어져있으므로 전부 더한 값이다.
예를 들어 자세히 설명하자면, 섬 4개에 번호를 붙여서 1-2-3-4 라 하면, 1에서 나올 수 있는 쌍은 ~2, ~3, ~4까지이고 각각 최소 다리의 개수는 1, 2, 3 이므로 1에서 출발하는 경우는 1+2+3=6 이다. 2에서 출발하는 경우는 1+2 이고, 3에서 출발하는 경우는 1 (3-4. 1개)이다. 그럼 섬이 4개일 때 bridges는 1+(1+2)+(1+2+3)=10 이다.

수식으로는 $$\sum_{k=1}^{n-1} \left(\sum_{i=1}^k i\right) = \frac{(n-1)n(n+1)}{6}$$ 가 된다.

그리고 갈라져있던 두 집합이 합쳐지는 경우가 있는데, 이 때는 각 집합에서의 pairs와 bridges를 빼고 합친 후의 pairs와 bridges를 더해주면 전체 값은 유지되면서 갱신한 후에 센 값과 같다.

다음은 코딩할 때 사용한 샘플 입출력이다.

예제 입력
5
1 2 4 3

예제 출력
1 1
3 4
4 5
10 20

2016년 4월 6일 수요일

2016년 3월 11일 금요일

4299 - AFC 윔블던

https://www.acmicpc.net/problem/4299

두 팀이 득점한 점수의 합과 차가 주어졌을 때, 두 팀의 성적을 출력한다.

두 팀의 성적은 a, b라 하고 합을 S, 차를 D 라고 하면 $$
\begin{cases}
a+b = S \\[2ex]
a-b = D & \text{(a $\ge$ b)}
\end{cases}$$ 이다.

다시 말하면, a = (S+D)/2, b = (S-D)/2 이다.

두 팀이 서로 경기를 했으니 a, b 는 짝수일수밖에 없고, 음수가 나와도 잘못된 입력이라는 의미이다.

1024 - 수열의 합

https://www.acmicpc.net/problem/1024

합이 N이고 길이가 L이 되게 만드는 수는 단순히 생각한다면 (N/L) * L 일것이다.
이 말은, N/L 의 양쪽으로 L 만큼의 범위내에서 정답이 존재한다는 것이다.
18을 예로 들면, L=2 일때는 [8 9] 혹은 [9 10] 중에 답이 있고, L=3 일때는 [3 4 5] ~ [6 7 8] 중에 있다는 것이다. (L=3 이면 답은 5+6+7)

이런 아이디어에서 출발해서 L을 점점 늘려가면서 정답을 찾았다.
구간을 설정하고 그 시작점이 원하는 답 N을 넘어가면 그 이상의 수는 더 살펴볼 필요가 없다. (왜냐면 그 수 이후로는 합쳐서 N이 나올수가 없다. 무조건 N 을 넘는다)

L=2 부터 L=100 까지 답을 찾고, 없으면 -1 을 출력했다.

2016년 3월 2일 수요일

11571 - 분수를 소수로

1110 - 더하기 사이클 과 유사하게 풀었다.

소수점 자리가 반복되는 지점은 나눗셈의 나머지가 같은 부분이 나온다는 것이다. 같은 나머지 값이 나오면 그 부분부터 다시 반복되는건 당연한 사실이다.

우선 A/B 를 기약분수의 형태로 바꾼다.
손으로 나누기 과정을 직접 하듯이 수를 계속 나누면서 결과값(string)에 수를 계속 붙인다.
boolean값 배열을 놓고 연산 도중에 이미 나온 나머지값이 나온다면 나누기 작업을 중단한다. 그리고 이전에 그 나머지가 나온 순간부터 나누기를 중단한 곳 (현재 문자열의 끝) 까지가 반복되는 지점이라는 뜻이다.

반복되는 구간이 없다면 (0) 을 출력하고, 아니라면 반복되는 구간을 출력하도록 했다.

11880 - 개미

솔루션 듣고 어이가 없었다. 왜냐하면 하루종일 메달렸기 때문이다.

처음에는 임의의 점 P(x, y) 를 거쳐서 가는 최단 거리를 계산하려고 방정식을 하루종일 엄청 풀었는 데, 결국 답은 한 마디면 끝났다.

상자를 펼치면 된다. 그럼 (a+b)2 + c2 이 이동한 거리2 이다.

10216 - Count Circle Groups

개인적으로 재미있게 풀었다. 알고스팟 남극기지 문제처럼 좌표평면과 범위가 있어서 엄청 어려워보였다. (주의. 남극기지와는 다른 문제이다!)

시간 제한이 8초인건 나중에 봤고, N이 3,000 이하이길래 충분하다고 생각하고 코딩을 시작했다.

아이디어는 이랬다. (사실 문제에서 말한 그대로 하는거라 아이디어랄 것도 없다.)

아군의 통신 영역이 서로 닿거나 겹친다면 통신 가능하고, 통신이 가능한 부대끼리는 하나의 그룹처럼 이동한다. 여기서 그룹의 개수를 헤아리는 문제인데, 그렇다면 진영을 하나의 노드로 본다면, "몇 개의 그래프가 존재하는가"로 문제를 바꿔 생각할 수 있다.

정점 $A_i$ 에서 $A_j$ 로 통신 가능하다면 이동하도록 인접 리스트를 만든다. ( $O(N^2)$ )
각 정점에서 연결된 모든 정점을 방문하고 개수를 센다. (여기서 하나의 그래프를 발견) . 방문하지 않은 나머지 정점을 확인한다. ( $O(N^2)$ )

그래프를 세는 부분을 코드로 표현하면 아래와 같다.

bool dfs(vector<int> adj[], int pos){
    if(visit[pos]) return false;
    visit[pos] = true;
    for(int i=0; i<adj[pos].size(); ++i)
        dfs(adj, adj[pos][i]);
    return true;
}

memset(visit, false, sizeof(visit));
int count=0;
for(i=0; i<N; ++i){
    count += dfs(adj, i);
}
printf("%d\n", count);

지금 생각해보니 분리집합(Disjoint-set) 으로 했어도 괜찮았을것 같다.

2016년 3월 1일 화요일

2168 - 타일 위의 대각선

x=8cm 이고 y=12cm 인 대각선은 x=2cm 이고 y=3cm 인 대각선을 4배한 것과 같다.

그리고 x, y에 대해서 대각선이 그려지는 타일의 개수는 x+y-1개 이다.

위 두가지 정보를 조합하면 정답이 나온다.

2014년 7월 23일 수요일

5724 - 파인만

정답은 "N까지의 {Nk2}들의 합" 이다.

N=1 일 때 칸은 1개이다.
N=2 일 때는 1개짜리 칸이 2X2. 총 4개만큼 늘어나고, 거기에 2X2짜리 네모가 1개 있다.
N=3 일 때는 1개짜리 칸이 3X3개 + 2X2짜리가 4개 + 3X3짜리가 1개....

더이상 자세한 설명은 생략한다.

2014년 7월 18일 금요일

1002 - 터렛

두 원이 만나는 교점의 갯수를 구하는 문제이다.

자세한 내용은 두 원의 위치관계 를 참고하면 된다.

2014년 7월 17일 목요일

1834 - 나머지와 몫이 같은 수

N = 1 일 때
N = 2 일 때
N = 3 일 때

자연수 1 부터 적당한 수까지 직접 해보면 규칙이 보인다.

규칙을 찾기 힘들다면 아래를 참고해보자.
  •  나머지(R)는 나누는 수(N)보다 클 수 없다.

그럼 이렇게 생각해 볼 수 있다.
  • N으로 나누었을 때 나올 수 있는 나머지의 종류는 0, ... N-1 이다.

이 말을 다시 해석하면
  • R = [0, ... N-1] 인 수열에 대해, Ak = k * Rk + Rk (k ≥ 0) 인 수열의 합이다.
    (Rk은 몫이지만 나머지와 같고, Ak 는 원래의 숫자를 도출한 것이다.)
수열 A에 있는 원소들의 합이 곧 정답이다. 수열 A부터 살펴보면,
Ak = k * Rk + Rk = k * (Rk + 1) 로 나타낼 수 있고
이 수열은 A0 = 0, A1 = 1 * 2, A2 = 2 * 3 ... 이런식이다.

점화식으로 풀어내자면,
A0 + A1 + ... + An-1> + An = ( 0 * 1 ) + ( 1 * 2 ) + ... ( (n-1) * n ) + ( n * (n+1) )
  = (n-1) * n * (n+1) / 2     ∵ 합 공식
  = (n2 - 1) * n / 2        ∵ 합차공식


PS. 나머지와 몫을 묶어서 설명했는데, 몫이 0 일 경우는 없을 것이므로, A0 은 무시해도 된다.
PS. 몫은 quotient, 나머지는 remainder.

2869 - 달팽이는 올라가고 싶다.

원 제목 : PUŽ

A는 낮에 올라갈 높이
B는 밤에 내려갈 높이
V는 넘어야 할 최소 높이(목표) 이다.

시뮬레이팅을 해보자면

   +A  -B  +A  +B  +A  -B  +A ...

이렇게 진행될 것이다. 즉 while( (res += (A-B)) < V ) 의 반복문일 것이다.

하지면 여기서 함정은 입력에 있다.
입력을 읽어보면 100,000,000 이다. 무려 10의 9승. 10^9 (비트연산자 XOR 아님)

반복문으로 시뮬레이팅을 하는 위 알고리즘은 O(N)의 시간복잡도를 가진다.
그럼 N ≤ 10^9 에 대해서는 분명 TLE(Time Limit Exceeded) 이다.

다시 살펴보자.

  +A  -B  +A  -B  +A  -B  +A ...

-B인 순간 달팽이가 V를 넘기진 않을 것이다. 바람이 불지 않는 이상
그럼 중요한 시점은 +A가 되는 순간이라는 건데, 달팽이의 현재 높이를 차례대로 나타내면 다음과 같다.

 A  A-B  A-B+A   A-B+A-B   A-B+A-B+A ...

즉, A + n*(A-B) 의 형태가 보인다. 여기서 n은 경과 일수(days)-1 을 의미한다.
A + n*(A-B) 가 V를 넘기는. 다시말해 A + n*(A-B) ≥ V 인 n 을 찾으면 끝난다.

n에 대한 방정식으로 바꾸자면
n ≥ (V-A) / (A-B)

소수점 처리를 잘 이용하면 정답 n을 찾을 수 있을 것이다.

1193 - 분수찾기

1/1
1/2 2/1
3/1 2/2 1/3 ...
여기서 구간을 나눌 수 있다. 구간을 군으로 생각하면 아래와 같다.
n군은 n/1 (또는 1/n)부터 시작하여 1/n (또는 n/1)로 끝난다. (n군의 구간 길이는 n)

설명하기 앞서,
우리는 여기서 어떤 규칙을 발견할 수 있다.
위에서부터 n번째 군의 각 분수의 분자/분모 합은 모두 n+1 로 일정하다.
(예를 들면 3/1과 2/2과 1/3은 모두 3+1=2+2=1+3 = 4 이다.)
이 부분은 나중에 다시 언급하겠다.

군수열의 군을 An 이라 할 때 An = n * (n+1) / 2 이다.
주어진 X가 속하는 군은 An ≤ X < An+1 이므로 An ≤ X 만 구하면 된다.
n에 대한 방정식으로 바꿔말하면
n * (n+1) / 2 ≤ X
n2 + n - 2X ≤ 0 이다. n은 항상 양수이고, 이를 근의 공식으로 구하면
n = ( -1 + √(1 + 8X) ) / 2
여기서 n은 0부터 시작했으므로 X 는 " (n + 1) / 1" 의 형태(또는 반대)로 시작하는 분수이다.
(즉, 여기서 n은 군을 의미하고, 일종의 경계선이라 생각하면 편하다.)

이제 우리는 An 을 알고 n을 알기에 답을 충분히 유추할 수 있다.
(예를 들면, 7 의 경우에는 3군(n)에 속하고 (3/1 부터 시작하는..) 3군의 경계는 6 (An )이다.)
그럼 이제 이전에 발견했던 규칙(n군의 각 분수는 분자/분모 합이 일정하다.)을 이용하면 분자/분모를 유추할 수 있다.

PS. (또는 ~~) 의 표현은 n 이 짝수라면 1/n, 홀수라면 n/1으로 생각하면 된다.

1010 - 다리 놓기

동쪽에 다리가 N개 있고, 서쪽에 다리가 M개 있을 때
답은 NCM (조합론:Combination) 과 같다.

주의해야 할 점이라면, 팩토리얼 / 팩토리얼 연산은 오버플로우를 조심해야한다.
30!만 되어도 수가 엄청 커져서 나중에 100! 정도는 long long이라도 저장 못한다.

그럼 어떻게 해야할까?

조합(combination)은 nCr 에서 분자가 n! 이고 분모는 (n-r)! * r! 이다. 여기서 n > r 을 만족한다면 분자는 항상 분모의 소인수를 가진다. (수학적으로 증명하기엔 포스팅이 길어지므로 생략)

즉, 분자의 각 소인수들은 분모의 소인수들 중에 하나로 나눌 수 있다(!)
근데 nCr 의 결과가 자연수로 나누어 떨어지는 건 아무도 의심하지 못한다.

분자와 분모를 소인수 분해하여 개별적으로 약분하고 연산한다면 오버플로우를 방지할 수 있다.

게시글 목록