포스트

BOJ 1336 수열의 개수 NKD

sorohue가 PS하는 블로그

BOJ 1336 수열의 개수 NKD

문제 링크입니다.

거품 낀 제한

길이가 $K$고 원소의 합이 최소인 수열 $A$는 $[1, 2, 3, \cdots , K]$ 임이 자명하고, 이때 원소의 합은 $K(K+1)/2$입니다. $N \ge K(K+1)/2$여야 하는데, 제한에 의하면 $K$가 500만 돼도 $N$이 $100\,000$을 넘깁니다. $K$ 제한을 500인 것처럼 생각합시다.

계단 쌓기

수열 [1, 2, 3, …, $K]$를 가져옵시다. 이 수열을 계단처럼 생각해, 계단을 규칙에 맞게 변형하는 방법을 생각해 봅시다.

계단의 첫 칸부터 시작해, 순서대로 높이를 결정합니다. 현재 상황에서는 최소 0에서 최대 $D-1$까지 높이를 올릴 수 있습니다.

계단이 증가해야 한다는 조건이 있습니다. 어떤 칸을 올릴 때, 이 칸과 그 뒤의 칸들은 모두 높이 차가 1인 계단을 이루고 있기 때문에, 계단 모양이 깨지지 않기 위해서는 그 뒤의 칸까지 모두 올려야 합니다.

따라서, 1번째 칸을 1 올리면 수열의 총합은 $K$ 증가하고, 2번째 칸을 1 올리면 수열의 총합은 $K-1$ 증가하고 … 이런 식입니다.

DP

이제 문제를 1, 2, …, $K$를 각각 $D-1$개 이하로 사용해 그 합을 $N-K(K+1)/2$로 만드는 방법의 수를 구하는 것으로 변형시켰습니다. 1부터 $K$까지 각 수 $X$마다, 작은 $i$부터 보면서 $i$를 만드는 방법의 수를 $i, i-X, i-2X,…,i-(D-1)X$를 만드는 방법의 수의 합으로 계산할 수 있습니다.

이를 위해 $X$씩 점프하는 누적 합을 관리하면 각 $X$마다 $\mathcal{O}(N)$에 모든 갱신을 완료할 수 있습니다.

$X$는 아무리 커봤자 500이니 제한 시간 안에 문제를 해결할 수 있습니다. 정확히는, $K(K+1)/2 \le N$으로부터 $K$의 상한이 $\mathcal{O}(\sqrt{N})$임을 알 수 있습니다. 따라서 시간 복잡도는 $\mathcal{O}(N\sqrt{N})$입니다.

코드

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
29
30
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
using pll = pair<ll, ll>;
const ll mod = 1e9+7;

ll n, k, m;

ll d[101010], psum[101010];

int main(){
	cin.tie(0); cout.tie(0); ios::sync_with_stdio(false);
	cin >> n >> k >> m;
	if(k*(k+1)/2 > n) return !(cout << 0);
	if(k*(k+1)/2*m < n) return !(cout << 0);
	n -= k*(k+1)/2;
	d[0] = psum[0] = 1;
	for(ll x = 1; x <= k; x++){
		for(int i = 1; i <= n; i++){
			psum[i] = d[i];
			if(i >= x) psum[i] += psum[i-x];
			psum[i] %= mod;
			d[i] = psum[i];
			if(i >= m*x) d[i] -= psum[i-m*x];
			d[i] += 2*mod;
			d[i] %= mod;
		}
	}
	cout << d[n];
}
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.