OUPC β 解説
Grundy Number (200)
- writer: ningenMe
- URL
https://www.hackerrank.com/contests/oupc-beta/challenges/grundy-number
- 解説:
与えられる入力をsetや配列に格納し、数字の既出管理を行いましょう。
数列の最大値をとするとで解くことが出来ます。
writer解
#include <iostream> #include <vector> using namespace std; int main(void) { int N; cin >> N; vector<int> C(101,1); for(int i = 0; i < N; ++i) { int A; cin >> A; C[A] = 0; } for(int i = 0; i <= 100; ++i) { if(C[i]) { cout << i << endl; break; } } return 0; }
Doubling Step (300)
- writer: ningenMe
- URL
https://www.hackerrank.com/contests/oupc-beta/challenges/doubling-step
- 解説:
dpで解きましょう。番目の球に乗るような場合の数。
1つの球から移動できる選択肢は高々個なので、全体としてで解くことが出来ます。
writer解
#include <iostream> #include <vector> using namespace std; constexpr long long MOD = 998244353; int main(void) { long long N; cin >> N; vector<long long> dp(N+1,0); dp[1] = 1; for(int i = 1; i < N; ++i) { for(int j = 0; (i + (1 << j)) <= N; j++) { (dp[i + (1 << j)] += dp[i]) %= MOD; } } cout << dp[N] << endl; return 0; }
Product Grid (500)
- writer: ningenMe
- URL
https://www.hackerrank.com/contests/oupc-beta/challenges/product-grid
- 解説:
まず定数の値は、入力で与えられるの最小公倍数になります。
ここではとても大きくなるため、実装上ではを素因数で管理します。
の値が分かったので、各列において以外の残りの数字の積がになれば条件を満たします。
これは番目の列を考えたとき、の各素因数をのいずれかに割り当てることをすればよいです。
よって割り当てる素因数の数を個としたとき、重複組み合わせを考えると二項係数で場合の数を数えることができます。
よって各列においてそれぞれ独立に計算することで求める場合の数を得ることが出来ました。
しかし以下の素数の数は個であるため、上記の解法では最悪計算量となりTLEします。
ここで各列において、1行目の値に含まれる素因数がに含まれる素因数に比べて比較的少ないことを利用します。
であることを考えると、に含まれる素因数の種類の個数は高々6個です。
なので各列においてと仮定しての各素因数をに振り分けます。この値は全列で同じ値なのでで計算できます。
その後各列において、実際のの値の差分だけ計算すればよく、これはで計算出来ます。
よって、全体としてで解くことが出来ます。
writer解
#include <iostream> #include <vector> #include <map> using namespace std; template <class T> void chmax(T& a, const T b){a=max<T>(a,b);} constexpr long long MOD = 998244353; //Prime Factorization O(sqrt(N)) vector<pair<long long,long long>> PrimeFactorization(long long N) { vector<pair<long long,long long>> ret; for (long long i = 2,M = N; i*i <= M; ++i) { if (N%i == 0) { long long cnt = 0; while (N%i == 0) N /= i, cnt++; ret.push_back({i,cnt}); } } if (N != 1) ret.push_back({N,1}); return ret; } //Combination Mod class CombinationMod { public: vector<long long> fac,finv,inv; long long mod; CombinationMod(int N,long long mod) : fac(N + 1), finv(N + 1), inv(N + 1), mod(mod) { fac[0] = fac[1] = finv[0] = finv[1] = inv[1] = 1; for (int i = 2; i <= N; ++i) { fac[i] = fac[i - 1] * i % mod; inv[i] = mod - inv[mod%i] * (mod / i) % mod; finv[i] = finv[i - 1] * inv[i] % mod; } } long long num(int n, int k) { return ((n < 0 || k < 0 || n < k) ? 0 : fac[n] * (finv[k] * finv[n - k] % mod) % mod); } }; //Pow_Mod O(log(n)) long long PowMod(long long x, long long n, long long mod) { long long res = 1; for (; n > 0; n >>= 1, (x *= x) %= mod) if (n & 1) (res *= x) %= mod; return res; } //Inv_Mod O(log(mod)) long long InvMod(long long x, long long mod){ return PowMod(x,mod-2,mod); } //O(WsqrtA + H + LlogL)解法 int main(void) { long long H,W; cin >> H >> W; vector<long long> A(W); for(int i = 0; i < W; ++i) { cin >> A[i]; } //素因数を列挙 O(W*sqrt(A)) vector<vector<pair<long long,long long>>> P(W); for(int i = 0; i < W; ++i) { P[i] = PrimeFactorization(A[i]); } //L<=(Kに含まれる素数の個数の最大値)<=(maxA以下の素数の個数)=9592 //最小のKの素因数分解を構築 O(W + LlogL) map<long long, long long> K; for(int i = 0; i < W; ++i) { for(auto e: P[i]){ long long p = e.first; long long c = e.second; chmax(K[p],c); } } //二項係数 O(H) 2^16 = 65536 < maxA < 131072 = 2^17 < 2^20 CombinationMod CM(H+20,MOD); //A=1のときの数え上げを前計算 O(LlogL) long long cnt = 1; for(auto e:K) { long long N = e.second; //N個の素因数をH-1個の箱に配る O(1) cnt *= CM.num(N+H-2,N); cnt %= MOD; } //全てA=1と仮定して前計算 O(W) long long ans = 1; for(int i = 0; i < W; ++i) { ans *= cnt; ans %= MOD; } //i=0から数え上げ O(W) for(int i = 0; i < W; ++i){ //Aに素因数がある部分だけ数え直しO(1) 2*3*5*7*11*13 = 30030 < maxA < 510510 = 2*3*5*7*11*13*17 なので高々6回 for(auto e: P[i]){ long long N = K[e.first]; //N個の素因数をH-1個の箱に配る数を戻す O(1) ans *= InvMod(CM.num(N+H-2,N),MOD); ans %= MOD; //M個の素因数をH-1個の箱に配る O(1) long long M = K[e.first] - e.second; ans *= CM.num(M+H-2,M); ans %= MOD; } } cout << ans << endl; return 0; }