[BOJ 3955] 캔디 분배

Park Yeongseo·2023년 2월 13일
0

BOJ

목록 보기
4/7
post-thumbnail

https://www.acmicpc.net/problem/9202
레벨 : P5
알고리즘 분류 : 수학, 정수론, 확장 유클리드 호제법

[1] 문제 요약

각 테스트 케이스에 대해 KKCC가 주어졌을 때, K×X+1=C×YK \times X + 1 = C \times Y가 되는 자연수 X,YX, Y를 구하는 문제다. (단 Y <= 1,000,000,000)


[2] Preliminary

✏️ 참고할 만한 정수론 정수론 자료 (링크는 서울대학교 권오남 교수님의 SNUON 정수론 강의 내용 정리)

필요한 것들만 뽑아서 정리하자면 다음과 같다.

정수 a,b,x,y,ca, b, x, y, c에 대해서 다음이 성립한다.
(1)ax+by=1ax + by = 1인 정수 x,yx, y가 존재     a,b\iff a, b가 서로소.
(2)ax+by=cax + by = c이면 gcd(a,b)cgcd(a,b)|c
(3)ax0+by0=cax_0 + by_0 = c이면 x=x0+kbgcd(a,b),y=y0kagcd(a,b)x = x_0 + \frac{kb}{gcd(a,b)}, y = y_0-\frac{ka}{gcd(a,b)}인 정수 x,yx,y에 대해서도 ax+by=cax+by=c

위 (3)은 ax+by=cax+by = c의 특수해 x0,y0x_0, y_0을 구하면 ax+by=cax+by=c의 일반해 x,yx,y도 구할 수 있다는 것을 의미한다.


[3] 유클리드 호제법

0이 아닌 두 정수 a,ba, b의 최대 공약수를 구하는 유클리드 호제법은 아래와 같다.

a=bn1+r1이라고하자.(a,b)=(b,r1)이다.b=q2r1+r2라고하자.(b,r1)=(r1,r2)이다.r1=q3r2+r3이라고하자.(r1,r2)=(r2,r3)이다....작업을rn1=qn+1rn+rn+1대해rn+10때까지반복한다.이때얻을있는0아닌가장작은정수rn바로(a,b)이다.a = bn_1+r_1이라고\,하자.\,(a,b)=(b,r_1)이다. \\b = q_2r_1 + r_2라고\,하자.\,(b,r_1) = (r_1, r_2)이다. \\r_1 = q_3r_2 + r_3이라고\,하자.\,(r_1,r_2)=(r_2,r_3)이다. \\... \\위\,작업을\,r_{n-1} = q_{n+1}r_{n} + r_{n+1}에\,대해\,r_{n+1}가\,0이\,될\,때까지\,반복한다. \\이때\,얻을\,수\,있는\,0이\,아닌\,가장\,작은\,정수\,r_n이\,바로\,(a,b)이다.

이는 코드로 다음과 같이 나타낼 수 있다.

int gcd(int a, int b){
    int dividend = a;
    int divisor = b;
    int quotient;
    int remainder;

    while (a && b){
        quotient = dividend / divisor;
        remainder = dividend % divisor;
        printf("%d = %d * %d + %d\n", dividend, quotient, divisor, remainder);
        if (remainder == 0) return divisor;
        dividend = divisor;
        divisor = remainder;        
    }
    return -1; //입력 a, b 중 하나라도 0인 경우 -1을 리턴해서 연산이 제대로 되지 않았음을 표시했습니다.
}

[4] 확장 유클리드 호제법

앞서 ax+by=cax + by = c이면 gcd(a,b)cgcd(a,b)|c라는 정리를 적어놨는데, 확장 유클리드 호제법은 c=gcd(a,b)c=gcd(a,b)일 때, 즉 ax+by=gcd(a,b)ax+by = gcd(a,b)가 될 때의 정수해 x,yx, y를 구하는 알고리즘이다.

유클리드 호제법에서 나타나는 나머지 r1,...,rn+1r_1, ..., r_{n+1} (rn=gcd(a,b),rn+1=0r_{n} = gcd(a,b), r_{n+1} = 0임에 주의)이 모두 a,ba, b의 일차결합으로 표현될 수 있고, 위에서도 볼 수 있듯 각 나머지들 사이에서는 ri1=qi+1ri+ri+1r_{i-1} = q_{i+1}r_i + r_{i+1}의 관계가 성립한다.

rir_i를 일반화해 다음과 같이 쓰도록 하자.

ri=sia+tibr_i = s_ia + t_ib

그러면 앞서의 나머지들 사이의 관계 ri1=qi+1rn+ri+1r_{i-1} = q_{i+1}r_n + r_{i+1}를 이용해 다음과 같은 si,tis_i, t_i의 점화식을 얻을 수 있다.

ri+1=si+1a+ti+1b=rn1qn+1rn=si1a+ti1bqn+1(sia+tib)=(si1qn+1si)a+(ti1qn+1ti)bsi+1=si1qn+1si,ti+1=ti1qn+1ti\begin{aligned} r_{i+1} =s_{i+1}a + t_{i+1}b =&r_{n-1}-q_{n+1}r_n\\ =&s_{i-1}a+t_{i-1}b-q_{n+1}(s_ia+t_ib)\\ =&(s_{i-1}-q_{n+1}s_i)a+(t_{i-1}-q_{n+1}t_i)b \end{aligned}\\ \therefore s_{i+1}=s_{i-1}-q_{n+1}s_i,t_{i+1}=t_{i-1}-q_{n+1}t_i

여기서 r1=a,r0=br_1 = a, r_0 = b라고 놓으면 si,tis_i, t_i의 초항들 s1,s0,t1,t0s_{-1}, s_0, t_{-1}, t_0을 얻을 수 있다.

r1=1×a+0×br0=0×a+1×b,s1=1,s0=0,t1=0,t0=1r_{-1} = 1 \times a + 0\times b\\ r_0 = 0 \times a + 1\times b \\즉,\,s_{-1} = 1, s_0 = 0, t_{-1}=0, t_0 = 1

위를 그대로 코드로 쓰면 아래와 같다.

/*
ax + by = (a,b)가 되는 
{{x, y}, (a,b)}를 찾는 함수
*/
pair<pair<int,int>, int> extendedGCD(int a, int b){
    int dividend = a, divisor = b;
    int quotient, remainder;
    int s_prev = 1, s_cur = 0, s_next;
		int t_prev = 0, t_cur = 1, t_next;
		
    while (a && b){
        quotient = dividend / divisor;
        remainder = dividend % divisor;
        if (remainder == 0) return {{s_cur, t_cur}, divisor};
        s_next = s_prev - quotient * s_cur; 
        t_next = t_prev - quotient * t_cur;
        s_prev = s_cur, s_cur = s_next;
        t_prev = t_cur, t_cur = t_next;     
        dividend = divisor;
        divisor = remainder;
    }
    return {{-1, -1}, -1}; //잘못된 입력의 경우
}

[5] 문제 풀이

앞서 문제 요약에서 말했듯 이 문제는 자연수 KKCC가 주어졌을 때, K×X+1=C×YK \times X + 1 = C \times Y가 되는 자연수 X,YX, Y를 구하는 문제, 즉 KX+CY=1-KX + CY = 1의 자연수 해 X,YX, Y를 구하는 문제다.(단 Y <= 1,000,000,000)

우리는 [2]Preliminary를 통해 ax+by=1ax+by=1인 정수 x,yx,y가 존재한다는 것은 a,ba,b가 서로소라는 것과 동치이고, ax+by=cax+by=c의 특수해를 안다면 그로부터 그 일반해도 얻을 수 있음을 알고 있다.

확장 유클리드 호제법을 통해서 우리는 K,CK, C의 최대공약수와 KX+CY=1KX + CY = 1의 특수해 x,yx, y를 구할 수 있고, 문제에서는 KX+CY=1-KX + CY = 1의 자연수 해를 구하는 것이 목적이니 특수해 x,yx, y를 통해 X<0,0<Y<=1,000,000,000X < 0, 0 < Y <= 1,000,000,000가 되는 정수를 구하면 된다.

[6] CODE

#include <iostream>
using namespace std;

int n, k, c, T;

pair<pair<int, int>, int> extendedGCD(int a, int b){
    int dividend = a, divisor = b;
    int q, r;
    int s_prev = 1, s_cur = 0, s_next;
    int t_prev = 0, t_cur = 1, t_next;    

    while (a && b){
        q = dividend / divisor;
        r = dividend % divisor;
        if (!r) return {{s_cur, t_cur}, divisor};
        s_next = s_prev - q * s_cur;
        t_next = t_prev - q * t_cur;
        s_prev = s_cur, s_cur = s_next;
        t_prev = t_cur, t_cur = t_next;
        dividend = divisor;
        divisor = r;
    }
    return {{-1, -1}, -1};
}

int main(){
    scanf("%d", &T);
    while (T--){
        scanf("%d%d", &k, &c);
        pair<pair<int, int>, int> extdGCD = extendedGCD(k, c); //확장 유클리드 호제법을 사용
        /*
        만약 K, C가 서로소가 아니라면 
        -KX + CY = 1의 정수해 X, Y는 존재하지 않는다.
        */
        if (extdGCD.second != 1) printf("IMPOSSIBLE\n");
        else {
            int x = extdGCD.first.first;   	//-KX+CY=1의 특수해 x
            int y = extdGCD.first.second;  	//특수해 y
            while (!(x < 0 && y > 0)){ 		//x <0, y> 0이 되어야 한다.
                x -= c;						//[2]Preliminary의 (3)을 참고
                y += k;
            }
            //문제의 제약에 따라 y는 1,000,000,000 이하가 되어야 하므로 확인해본다.
            while ((x < 0 && y > 0) && !(y <= 1000000000)){
                x += c;
                y -= k;
            }
            //x < 0, 0 < y <= 1,000,000,000이 되는 해가 존재하지 않는 경우 "IMPOSSIBLE"
            if (!(x < 0 && y > 0 && y <= 1000000000)) printf("IMPOSSIBLE\n"); 
            else printf("%d\n", y);
        }
    }
}

Clear!

0개의 댓글