연속 K칸의 합으로 만든 수열이 증가해야 한다는 조건은, 처음에는 “윈도우가 밀릴 때마다 합이 커지게 순열을 직접 짜야 하나?”처럼 보일 수 있습니다. 그러나 한 번 차분을 취하면 조건이 인덱스를 K로 나눈 나머지별 부분수열이 각각 증가한다는 단순한 구조로 바뀌고, 답은 다항계수 한 줄로 정리됩니다. 이 글에서는 그 정당성과 \(10^9+7\) 모듈러에서의 구현을 정리합니다.
문제 정보
문제 링크: https://www.acmicpc.net/problem/32720
문제 요약:
- 길이 \(N\)의 순열 \(A\)에 대해, \(B_i = A_i + A_{i+1} + \cdots + A_{i+K-1}\)로 길이 \(N-K+1\)의 수열 \(B\)를 만든다.
- \(B\)가 엄격히 증가하는 순열 \(A\)의 개수를 \(10^9+7\)로 나눈 나머지를 구한다.
제한 조건:
- 시간 제한: 2초
- 메모리 제한: 1024MB
- \(1 \le K \le N \le 10^6\)
입출력 예제
입력 1:
| |
출력 1:
| |
예제 확인: \(A=(1,3,4,2)\)이면 \(B_1=1+3+4=8\), \(B_2=3+4+2=9\)로 \(B=(8,9)\)가 증가합니다. 조건을 만족하는 순열은 모두 \(12\)가지입니다.
접근 방식
핵심 관찰
\(B\)가 증가한다는 것은 모든 \(i\)에 대해 \(B_{i+1} > B_i\)입니다. 두 항의 차이를 보면
\[ B_{i+1} - B_i = A_{i+K} - A_i \]이므로 \(A_{i+K} > A_i\) 가 필요합니다. 즉, 인덱스 \(i\)와 \(i+K\), \(i+2K\), … 로 이어지는 같은 나머지 클래스( \(\bmod K\) ) 안에서 값은 위치 순으로 감소할 수 없고, 엄격히 증가해야 합니다.
각 나머지 \(j \in \{1,\ldots,K\}\)에 대해 해당 위치의 개수는 \(\lfloor (N-j)/K \rfloor + 1\)입니다. 이를 정리하면, 몫 \(q = \lfloor N/K \rfloor\)과 나머지 \(r = N \bmod K\)를 쓸 때 \(r\)개의 그룹은 크기 \(q+1\), \(K-r\)개의 그룹은 크기 \(q\) 가 됩니다. 각 그룹 안에서는 값의 상대 순서가 증가로 고정되므로, 문제는 “\(1\)부터 \(N\)까지의 수를 이 \(K\)개 묶음 크기에 맞게 나누어 담는 경우의 수”와 같습니다. 그 개수는 다항계수
\[ \frac{N!}{((q+1)!)^r \cdot (q!)^{K-r}} \]입니다.
알고리즘 흐름 (Mermaid)
flowchart TD
inp["입력 N과 K"] --> qr["몫 q와 나머지 r 계산"]
qr --> fac["N 팩토리얼과 q 팩토리얼, q 플러스 1 팩토리얼을 MOD로 계산"]
fac --> den["분모는 factQ1의 r승과 factQ의 K빼기 r승의 곱"]
den --> inv["페르마 소정리로 분모의 모듈러 역원"]
inv --> out["N 팩토리얼에 역원을 곱해 출력"]
단계별 로직
- 전처리: \(q = N/K\), \(r = N \% K\)를 구한다.
- 팩토리얼: \(N!\), \((q+1)!\), \(q!\)을 \(10^9+7\)로 나눈 나머지로 계산한다. \(N \le 10^6\)이므로 선형 루프로 충분하다.
- 나눗셈: 분모 \(D = ((q+1)!)^r \cdot (q!)^{K-r}\)에 대해 답은 \(N! \cdot D^{-1} \pmod{10^9+7}\). 소수 모듈러이므로 \(D^{p-2}\)로 역원을 구한다.
복잡도 분석
| 항목 | 복잡도 | 비고 |
|---|---|---|
| 시간 복잡도 | \(O(N + \log \mathrm{MOD})\) | 팩토리얼 \(O(N)\), 거듭제곱으로 역원 \(O(\log \mathrm{MOD})\) |
| 공간 복잡도 | \(O(1)\) | 상수 개 변수만 사용 |
구현 코드
모듈러 곱셈에서 중간에 long long 범위를 넘지 않도록 피연산자를 줄곱 나머지를 취합니다. 아래 코드는 위 다항계수를 직접 계산합니다.
C++
| |
코너 케이스 및 실수 포인트
| 케이스 | 설명 | 처리 방법 |
|---|---|---|
| \(K = 1\) | \(B\)의 길이가 1 | 조건이 비어 있고 답은 \(1\) (공식도 \(N!/N! = 1\)) |
| \(K = N\) | 각 그룹 크기 1 | 모든 순열 가능, 답 \(N!\) |
| \(1 \le K \le N\) | 항상 \(q = \lfloor N/K \rfloor \ge 1\) | \(q!\), \((q+1)!\) 루프가 빈 범위 없이 동작 |
| 타입 | 곱셈 중 오버플로우 | 중간마다 % MOD와 long long 사용 |
마무리
이 문제는 “윈도우 합의 증가”라는 겉모습과 달리, 차분 한 번으로 mod \(K\) 그룹별 증가 조건으로 환원되는 전형적인 관찰 문제입니다. 그룹 크기만 알면 답은 다항계수이고, 구현은 팩토리얼 + 페르마 역원으로 끝납니다. 출처는 2024 KUPC K번입니다.
참고 및 출처
이 글을 읽은 후 점검해 볼 질문
- \(B_{i+1} - B_i\)를 전개해 \(A_{i+K} > A_i\)가 됨을 직접 써서 설명할 수 있는가.
- 나머지 \(j\)마다 위치 개수가 왜 \(q+1\) 또는 \(q\)로 나뉘는지 \(N, K\)로 분류해 볼 수 있는가.
- 왜 각 그룹 내부 순서가 증가로 고정되면 전체 순열 수가 다항계수와 일치하는가.
![Featured image of post [Algorithm] C++ 백준 32720번: 순열과 증가수열](/post/algorithm/boj-32720-permutation-increasing-window-sums/wordcloud_hu_fc3a8f9b5f6c5400.webp)
![[Algorithm] C++ 백준 30239번: 트리와 XOR 리루팅 DP 풀이](/post/algorithm/boj-30239-tree-xor-rerooting/wordcloud_hu_58bb17ea9e321d6d.webp)
![[Algorithm] C++ 백준 32720번: 순열과 증가수열](/post/algorithm/boj-32720-permutation-increasing-window-sums/wordcloud_hu_ef22f88cba3eba0c.webp)
![[Algorithm] C++ 백준 15403번: Escape Room](/post/algorithm/boj-15403-escape-room-permutation/wordcloud_hu_dee1da2f44ce9513.webp)
![[Algorithm] C++ / Python 백준 11238번: Fibo](/post/algorithm/2026-03-10-boj-11238-fibo-cpp-python-solution/wordcloud_hu_becd9f0d5bbc5cc4.webp)
![[Algorithm] C++ / Python 백준 24491번: Searching for Soulmates](/post/algorithm/2026-03-10-boj-24491-searching-for-soulmates-cpp-python-solution/wordcloud_hu_d731700bc50653bc.webp)
![[Algorithm] C++ 백준 32190번: Ian Sequences](/post/algorithm/2025-12-19-boj-32190-ian-sequences-cpp-solution/wordcloud_hu_370f2c99b3f65ff8.webp)