BOJ 33688 불의 군주 라그나로스 2
sorohue가 PS하는 블로그
BOJ 33688 불의 군주 라그나로스 2
문제 링크입니다.
문제 요약
라그나로스 $N$체가 내 필드에 있습니다. 적 영웅의 체력과 하수인이 주어졌을 때, 턴 종료 시 승리하는 경우의 수를 구하세요.
풀이
라그나로스의 피해량, 적 영웅과 하수인의 체력을 버리고 대신 각 개체를 처치하기 위해 필요한 공격 횟수를 들고 옵니다. 라그나로스를 공으로, 각 개체를 상자로 치환해 조합론적 모델로 해석합니다.
상자는 $0$번부터 $M$번까지 총 $M+1$개 있습니다. 각 상자는 용량 $C_i$가 정해져 있어, 용량을 초과하는 공은 집어넣을 수 없습니다. 공은 $1$번부터 $N$번까지 순서대로 자리가 남아 있는 상자 중 하나에 집어넣습니다. $0$번 상자에 공이 가득 차는 순간 행동을 종료하며, 이때 $0$번 상자에 공이 가득 차는 경우의 수를 구하면 됩니다.
가능한 모든 $n$에 대해 $n$번째 공으로 $0$번 상자에 공이 가득 차는 경우의 수를 더하면 되겠네요. 이를 위해 $0$번 상자의 용량을 1 깎고, 공의 순서를 따져야 하니 지수생성함수를 고려합니다.
- $0$번 상자의 경우 반드시 $C_0 - 1$개의 공이 들어차야 하므로, 생성함수는 $\frac{1}{(C_0 - 1)!}x^{C_0-1}$입니다.
- $i$번 상자($1 \le i \le M$)의 경우 그냥 $\sum\limits_{t=0}^{C_i}{\frac{1}{t!}x^t}$가 생성함수입니다.
이제 이 다항식들을 싹 다 곱하면, $t$차항의 계수 $D_t$에 대해 $t! D_t$가 $t+1$번째 공으로 $0$번 상자를 채우고 끝내는 경우의 수가 됩니다.
다항식을 곱하는 건 차수가 작은 것끼리 곱하면서 필요 이상 차수의 항은 잘라내는 식으로 하면 좀 덜 잘 짜도 간당간당하게 시간 제한 안에 들어갑니다.
코드
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
using pii = pair<int, int>;
using pll = pair<ll, ll>;
const ll mod = 998244353;
const ll w = 3;
int NMAX = 50010;
ll pw(ll n, ll r){
ll ret = 1;
while(r){
if(r&1) ret = ret*n%mod;
n = n*n%mod;
r >>= 1;
}
return ret;
}
inline ll inv(ll n){return pw(n, mod-2);}
vector<ll> p[50505];
ll fac[50505], ifac[50505];
void ntt(vector<ll>& f, bool flag = 0){
int n = f.size(), j = 0;
vector<ll> root(n>>1);
for(int i = 1; i < n; i++){
int bit = n>>1;
while(j >= bit){
j -= bit; bit >>= 1;
}
j += bit;
if(i < j) swap(f[i], f[j]);
}
ll ang = pw(w, (mod - 1) / n); if(flag) ang = inv(ang);
root[0] = 1; for(int i=1; i<(n >> 1); i++) root[i] = root[i-1] * ang % mod;
for(int i=2; i<=n; i<<=1){
int step = n / i;
for(int j=0; j<n; j+=i){
for(int k=0; k<(i >> 1); k++){
ll u = f[j | k], v = f[j | k | i >> 1] * root[step * k] % mod;
f[j | k] = (u + v) % mod;
f[j | k | i >> 1] = (u - v) % mod;
if(f[j | k | i >> 1] < 0) f[j | k | i >> 1] += mod;
}
}
}
ll t = inv(n);
if(flag) for(int i=0; i<n; i++) f[i] = f[i] * t % mod;
}
void mult(vector<ll>& a, vector<ll>& b){
int n = 2, N = a.size()+b.size()-1; while(n < a.size()+b.size()) n <<= 1;
a.resize(n); b.resize(n); ntt(a); ntt(b);
for(int i = 0; i < n; i++) a[i] = a[i]*b[i]%mod;
ntt(a, 1); a.resize(min(N, NMAX)); return;
}
priority_queue<pii> pq;
int main(){
cin.tie(0); cout.tie(0); ios::sync_with_stdio(false);
fac[0] = 1; for(int i = 1; i <= 50500; i++) fac[i] = fac[i-1]*i%mod;
ifac[50500] = pw(fac[50500], mod-2); for(int i = 50500; i; i--) ifac[i-1] = ifac[i]*i%mod;
int n, m, x, y; cin >> n >> m >> x >> y; y = (y-1)/x+1; p[0].resize(y); p[0][y-1] = ifac[y-1]; pq.emplace(-y, 0);
for(int i = 1; i <= m; i++){
int h; cin >> h; h = (h-1)/x+1; for(int j = 0; j <= h; j++) p[i].push_back(ifac[j]); pq.emplace(-h-1, i);
}
while(pq.size() > 1){
auto [d, i] = pq.top(); pq.pop();
auto [dd, j] = pq.top(); pq.pop();
mult(p[i], p[j]); pq.emplace(-p[i].size(), i);
}
ll ans = 0; int idx = pq.top().second; n = min(n, (int)p[idx].size());
for(int i = y-1; i < n; i++) ans = (ans+fac[i]*p[idx][i]%mod)%mod;
cout << ans;
}
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.