pstopia Notes for Problem Solving Contest

[Hard] SRMChallengePhase

Topcoder SRM 520 Div1

Problem

각 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) 으로 줄일 수 있다.
#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;
}
};