티스토리 뷰

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

 

15718번: 돌아온 떡파이어

각 테스트 케이스마다 한 줄에 하나씩 나이를 먹는 방법의 가짓 수를 100007로 나눈 나머지를 출력하시오. 100007은 일반적이지 않은 나눗수임에 유의하라.

www.acmicpc.net

예외 사항이 생각보다 좀 있어서 몇 번 틀렸습니다.

 

문제를 해석해보면 M째 날에 N세로 생을 마감한다고 하였고, 0개를 먹을 때 죽으므로, M-1일 째까지는 1 이상의 값을 가지고, 합이 N이 되는 경우의 수를 구하면 됩니다.

 

즉, x1 + x2 + ... + x(m-1) = n , xi >= 1 인 경우의 수를 구하는 것이고, 이것은 고등학교 수학을 잘 떠올려보면

중복조합의 개수를 구하는 것과 동일합니다.

 

그렇기 때문에 (x1 - 1) + (x2 - 1) + ... + (x(m-1) - 1) = n - (m-1), xi >= 0 인 (x1, ..., x(m-1)) 의 해의 개수는

(m-1)H(n - (m-1)) = (n-1)C(m-2) 입니다.

 

하지만 문제에 주어진 조건이 N, M이 최대 10억이므로 그냥 조합을 구하면 여지 없이 틀려버립니다.

 

그러므로 조합의 특정 소수 p에 대한 모듈러 값을 구하기 위해 루카스 정리를 사용하고 싶지만, 

100007 = 97 * 1031 이므로 바로 루카스 정리를 쓸 수는 없습니다.

따라서, 루카스 정리를 97, 1031 모듈로에 먼저 적용한 후, 중국인의 나머지 정리를 사용하면 100007 모듈로에 대한 값을 얻을 수 있습니다.

 

즉, x = (n-1)C(m-2) 라 하면 루카스 정리로 x % 97, x % 1031은 쉽게 구할 수 있고,

x = x1 mod 97, x = x2 mod 1031 인 x1, x2를 알고 있으므로 중국인의 나머지 정리로 x % 100007을 구할 수 있습니다.

 

코드는 다음과 같습니다.

(루카스 정리의 부분 이항계수를 구하는 과정을 좀 더 최적화를 시킬 수 있지만, 얼추 계산해보니 그렇게 계산 cost가 크지 않아 간단히 아래와 같이 단순 곱셈과 나눗셈만을 하였습니다. 해당 부분의 최적화는 각자 하고 싶은데로.....)

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

// x1+...+x(m-1) = n 이 되는 경우의 수
// xi >= 1
// 결국 (m-1)H(n-(m-1)) 구하는 문제
// = (n-1)C(m-2) % 100007 구하면 됨
// 100007 = 97 * 1031
// x = (n-1)C(m-2) 라 하면
// x % 97, x % 1031 해서 나온 값을 중나정으로 계산하면 됨
// x % 97, x % 1031 할 때는 루카스 정리 이용
// 루카스 정리 쓸 때 이항계수 부분 계산은 대략 1000번 정도 곱하기 드므로
// 그냥 곱하면서 모듈로 연산해도 될 듯?
// 이항계수 분모 부분은 inverse 구해서 곱하기

vector<int> convert(int num, int m)
{
	// num을 m진법 변환
	vector<int> vec;
	while (num > 0) {
		vec.push_back(num % m);
		num /= m;
	}

	reverse(vec.begin(), vec.end());

	return vec;
}

int inverse(int num, int p)
{
	// 확장 유클리드로 인버스 구하기 (존재 가정)
	// num^-1 mod p
	int r1 = p;
	int r2 = num;
	int t1 = 0;
	int t2 = 1;
	int q, r, t;

	while (r2 > 0) {
		q = r1 / r2;
		r = r1 % r2;
		r1 = r2;
		r2 = r;

		t = t1 - q * t2;
		t1 = t2;
		t2 = t;
	}

	if (t1 < 0)
		t1 += p;
	
	return t1;
}

int comb(int n, int r, int m)
{
	// nCr % m
	int result = 1;
	int inv = 1;
	for (int cnt = 0; cnt < r; cnt++) {
		result *= (n - cnt);
		result %= m;
		inv = inverse((r - cnt), m);
		result *= inv;
		result %= m;
	}

	return result;
}

int lucas(int n, int r, int m)
{
	// nCr % m 를 루카스 정리로 계산
	vector<int> vec_n = convert(n, m);
	vector<int> vec_r;
	vector<int> tmp = convert(r, m); // 일단 앞에 0 채우기 위해 임시 저장

	int result = 1; // 최종 루카스 정리 결과 
	int size_n = vec_n.size();
	int diff = vec_n.size() - tmp.size();
	for (int i = 0; i < diff; i++) {
		vec_r.push_back(0LL);
	}
	for (int i = 0; i < tmp.size(); i++) {
		vec_r.push_back(tmp[i]);
	}

	for (int i = 0; i < size_n; i++) {
		int num_n = vec_n[i];
		int num_r = vec_r[i];

		if (num_n < num_r) {
			// 0인 케이스. 더 이상 곱해도 0
			result = 0;
			break;
		}

		if (num_n == num_r || num_r == 0) {
			// 1인 케이스
			continue;
		}
		
		result *= comb(num_n, num_r, m);
		result %= m;
	}

	return result;
}

int CRT(vector<int> num, vector<int> m)
{
	// x = num[0] mod m0
	// x = num[1] mod m1
	// .... 를 만족하는 x = num[0] * num[1] * ... mod (m0 * m1 * ...) 구하기

	int num_size = num.size();
	vector<int> n(num_size, 0); // 중나정에서 n 역할 (ni = m0 * m1 * .. * m(n-1) * m(n+1) * ...)
	vector<int> s(num_size, 0); // 중나정에서 s 역할 (si = ni^-1 mod mi)

	int m_fac = 1;
	for (int i = 0; i < num_size; i++) {
		m_fac *= m[i];
	}
	for (int i = 0; i < num_size; i++) {
		n[i] = m_fac / m[i];
		s[i] = inverse(n[i], m[i]);
	}

	int result = 0;
	for (int i = 0; i < num_size; i++) {
		result += (num[i] * n[i] * s[i]) % m_fac;
		result %= m_fac;
	}

	return result;
}

int except(int N, int M)
{
	int ret = -1;
	// 나이 기준 예외 처리
	if (N == 0 && M == 1) {
		ret = 1;
	}

	if (N == 0 && M > 1) {
		ret = 0;
	}

	if (N == 1 && M == 2) {
		ret = 1;
	}

	if (N == 1 && M != 2) {
		ret = 0;
	}

	// 날짜 기준 예외 처리
	if (M == 1 && N == 0) {
		ret = 1;
	}

	if (M == 1 && N > 0) {
		ret = 0;
	}

	if (M == 2 && N == 0) {
		ret = 0;
	}

	if (M == 2 && N > 0) {
		ret = 1;
	}

	// 중복조합 안 나오는 케이스
	if (N - (M - 1) < 0) {
		ret = 0;
	}

	return ret;
}

int main()
{
	ios_base::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);

	int N, M;
	int T;

	int mod = 100007;
	int mod1 = 97;
	int mod2 = 1031;
	
	cin >> T;
	while (T--) {
		cin >> N >> M;

		int exc = except(N, M);
		if (exc != -1) {
			cout << exc << '\n';
			continue;
		}

		int x1 = lucas(N - 1, M - 2, mod1);
		int x2 = lucas(N - 1, M - 2, mod2);

		vector<int> num;
		num.push_back(x1);
		num.push_back(x2);

		vector<int> m;
		m.push_back(mod1);
		m.push_back(mod2);

		int result = CRT(num, m);

		cout << result << '\n';
	}
}

 

 

'알고리즘 > 백준' 카테고리의 다른 글

백준 1695 팰린드롬 만들기 C++  (0) 2022.12.11
백준 3108 로고 C++  (0) 2022.12.05
백준 7578 공장 C++  (0) 2022.11.09
백준 2957 이진 탐색 트리 C++  (1) 2022.11.05
백준 1562 계단 수 C++  (0) 2022.10.27
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
TAG
more
«   2025/02   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28
글 보관함