[Hard] SRMChallengePhase
Topcoder SRM 520 Div1
각 coder i 마다 4종류의 그룹으로 분류할 수 있다.
- sender : attempted[i] == 'Y' && challenged[i] == 'N'
- receiver : attempted[i] == 'N' && challenged[i] == 'Y'
- both : attempted[i] == 'Y' && challenged[i] == 'Y'
- unused : attempted[i] == 'N' && challenged[i] == 'N'
unused 그룹에 속한 coder는 답에 아무런 영향을 미치지 못하므로 관심에서 제외한다.
그리고 |sender| < |receiver| 라면 답은 0이다.
challenge를 시도하는 사람보다 challenge를 당하는 사람이 더 많은 것은
있을 수 없는 상황이기 때문이다.
우리가 구해야 하는 답은
시간 순으로 정렬된 sequence of challenge attempts 의 가능한 경우의 수이다.
특정한 sequence 하나를 table로 모델링해보자.
구체적으로, 먼저 M x 2 table 을 만든다.
만약 i번째 challenge가 A가 B를 challenge 하는 것이라면
i번째 행의 왼쪽 열에 A를, 오른쪽 열에 B를 적는다.
이런 식으로 table을 채우면 이것이 하나의 sequence가 된다.
그리고 자명하게 M = |sender| + |both| 가 될 것이다.
문제의 조건을 만족하면서 M x 2 table을 채우는 경우의 수를 구하면 우리가 원하는 답이 된다.
먼저 both 그룹에 속한 coder부터 배치해보자.
여기서는 우선 both coder 가 수행한 challenge,
both coder 가 당한 successful challenge 만 고려한다.
위의 2가지 케이스만 고려할 경우
i번 both coder 는 왼쪽 열에서 한번, 오른쪽 열에서 한번 등장해야한다.
그리고 왼쪽 열에서 등장한 행 번호를 a, 오른쪽 열에서 등장한 행 번호를 b라고 하면
a < b 를 만족해야한다.
이 경우의 수는 DP로 구할 수 있다.
D[n][k] = 구분되지 않는 k명의 both coder를 n x 2 table에 채우는 경우
= D[n-1][k] + (n-k)*D[n-1][k-1]
1) 맨 처음 행의 왼쪽 열이 비어있는 경우
-> D[n-1][k]
2) 맨 처음 행의 왼쪽 열에 both coder를 두는 경우
-> 이 both coder는 그 아래 어딘가에서 오른쪽 열에 등장해야 한다.
이 때, 등장할 수 있는 경우의 수가 n-k가지 이다.
(n-k) * D[n-1][k-1]
이제 sender, receiver, both 그룹 모두 함께 배치해보자.
(앞으로는 S=|sender|, R=|receiver|, B=|both| 로 나타냄)
먼저, B명의 both coder를 (S+B) x 2 table에 배치한다.
-> D[S+B][B] * B!
S명의 sender coder를 왼쪽 열의 남은 칸에 배치한다.
-> S!
오른쪽 열에 S칸이 남았는데, 여기에 R명의 receiver coder를 배치한다.
-> P(S, R)
오른쪽 열에 S-R칸이 남았는데, 이제 여기는 unsuccessful challenge 이므로 아무나 와도 된다.
-> (N-1)^(S-R)
따라서 답은 D[S+B][B] * B! * S! * P(S, R) * (N-1)^(S-R)
시간복잡도는 O(N^2)
여기서 한걸음 더 나아가 생각해보면
D[n][k] 는 사실 제2종 스털링 수와 같다는 사실을 알 수 있다.
구체적으로 D[n][k] = S(n, n-k) 이다.
생각해보면, both coder를 k명 배치하는 것은 결국 집합을 n-k개 만드는 것과 같기 때문이다.
이를 이용하면 시간복잡도를 O(NlgN) 으로 줄일 수 있다.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <vector> | |
#include <string> | |
#include <numeric> | |
using namespace std; | |
const int MOD = 1000000007; | |
long long modpow(long long r, long long n) { | |
long long ret = 1; | |
for (; n; n /= 2) { | |
if (n % 2) ret = ret * r % MOD; | |
r = r * r % MOD; | |
} | |
return ret; | |
} | |
inline long long modinv(int r) { | |
return modpow(r, MOD - 2); | |
} | |
long long fact[2501], factinv[2501]; | |
inline long long perm(int n, int m) { | |
if (m < 0 || m > n) return 0; | |
return fact[n] * factinv[n - m] % MOD; | |
} | |
inline long long comb(int n, int m) { | |
if (m < 0 || m > n) return 0; | |
return fact[n] * factinv[m] % MOD * factinv[n - m] % MOD; | |
} | |
long long stirling2nd(int n, int k) { | |
long long ret = 0; | |
for (int j = k, sign = 1; j >= 0; --j, sign *= -1) { | |
ret = (ret + sign * (comb(k, j) * modpow(j, n) % MOD) + MOD) % MOD; | |
} | |
return ret * factinv[k] % MOD; | |
} | |
class SRMChallengePhase { | |
public: | |
int countWays(vector<string> codersAttempted, vector<string> codersChallenged) { | |
string attempted = accumulate(codersAttempted.begin(), codersAttempted.end(), string()); | |
string challenged = accumulate(codersChallenged.begin(), codersChallenged.end(), string()); | |
int n = attempted.length(); | |
int natp = 0, ncha = 0, nboth = 0; | |
for (int i = 0; i < n; ++i) { | |
natp += (attempted[i] == 'Y' && challenged[i] == 'N'); | |
ncha += (attempted[i] == 'N' && challenged[i] == 'Y'); | |
nboth += (attempted[i] == 'Y' && challenged[i] == 'Y'); | |
} | |
fact[0] = 1; | |
for (int i = 1; i <= n; ++i) { | |
fact[i] = fact[i - 1] * i % MOD; | |
} | |
factinv[n] = modinv(fact[n]); | |
for (int i = n - 1; i >= 0; --i) { | |
factinv[i] = factinv[i + 1] * (i + 1) % MOD; | |
} | |
long long ans = 1; | |
ans = ans * stirling2nd(natp + nboth, natp) % MOD; | |
ans = ans * fact[natp] % MOD; | |
ans = ans * fact[nboth] % MOD; | |
ans = ans * perm(natp, ncha) % MOD; | |
ans = ans * modpow(n - 1, natp - ncha) % MOD; | |
return (int)ans; | |
} | |
}; |