接着剤の精進日記

競プロでの精進や研究に関係したことを書いていきます。

AtCoder Beginner Contest 184(ABC184)

atcoder.jp

A - Determinant

定義が書いてあるのでそれを出力
提出コード

void solve(){
    int a, b, c, d;
    cin >> a >> b >> c >> d;
    cout << a * d - b * c << endl;
}

B - Quizzes

点数をシミュレートする
提出コード

void solve(){
    int N, X;
    string S;
    cin >> N >> X >> S;
    REP(i,N){
        if(S[i] == 'o') X++;
        else{
            X = max(0, X-1);
        }
    }
    cout << X << endl;
}

C - Super Ryuma

頭壊れる
任意の点に最大3手以内にいける
$ (r_1, c_1) = (r_2, c_2) $の場合は0でそれ以外の場合を考える
初期状態から1手で行けるときは$ |r_1 - r_2| + |c_1 - r_c| \leq 3 $ もしくは対角成分上にあるときならば可能
2手で行けるときは、市松模様にマスに色を塗ったときに同じ色の場合2手で行ける
それ以外の場合、$ |r_1 - r_2| + |c_1 - r_c| \leq 3 $が必要になるので移動できる箇所を全探索し、上記の判定をもう一度考える
提出コード

void solve(){
    ll r1, c1, r2, c2;
    cin >> r1 >> c1 >> r2 >> c2;
    if(r1 == r2 and c1 == c2) cout << 0 << endl;
    else if(abs(r1 - r2) + abs(c1 - c2) <= 3) cout << 1 << endl;
    else if(r1 + c1 == r2 + c2) cout << 1 << endl;
    else if(r1 - c1 == r2 - c2) cout << 1 << endl;
    else if(abs(r2 - r1) % 2 == abs(c2 - c1) % 2) cout << 2 << endl;
    else{
        int ans = INF;
        for(int i=-3;i<=3;i++) for(int j=-3;j<=3;j++){
            if(i == 0 and j == 0) continue;
            ll nr = r1 + i, nc = c1 + j;
            if(abs(r1-nr) + abs(c1-nc) > 3) continue;
            if(r2 == nr and c2 == nc) chmin(ans, 1);
            else if(nr + nc == r2 + c2) chmin(ans, 2);
            else if(nr - nc == r2 - c2) chmin(ans, 2);
            else if(abs(nr - r2) + abs(nc - c2) <= 3) chmin(ans, 2);
            else chmin(ans, 3);
        }
        cout << ans << endl;
    }
}

D - increment of coins

期待値のDPをする
$ dp[a][b][c] := $ 金貨がa枚、銀貨がb枚、銅貨がc枚あるときの期待値とすると3つのうちどれが選ばれるかで
$ dp[a][b][c] = \frac{dp[a+1][b][c] \times a + dp[a][b+1][c] \times b + dp[a][b][c+1] \times c}{a + b + c} + 1 $となる
実装はメモ化再起のほうが楽そう
提出コード

void solve(){
    int A, B, C;
    cin >> A >> B >> C;
    if(A >= 100 or B >= 100 or C >= 100){
        cout << 0 << endl;
        return;
    }
    vector dp(101, vector(101, vector(101, -1.0)));
    auto dfs = [&](auto && self, int a, int b, int c) -> double{
        if(a >= 100 or b >= 100 or c >= 100) return 0;
        if(dp[a][b][c] > -1) return dp[a][b][c];
        double sum = a + b + c;
        return dp[a][b][c] = (self(self, a+1, b, c) * a + self(self, a, b+1, c) * b + self(self, a, b, c+1) * c) / sum + 1;
    };
    printf("%.12lf\n", dfs(dfs, A, B, C));
}

E - Third Avenue

各テレポーターごとに移動可能なマスを予め保存しておく
今いるマスがテレポーターなら同じテレポーターのマスにコスト1で移動出来るとしてBFSをする
テレポーターを使う場合同じテレポーターは一回までしか使わなくていいので、すでに使ったことのあるテレポーターの場合、もう使えないものとして無視すればいい
提出コード

void solve(){
    int H, W;
    cin >> H >> W;
    vector<string> fi(H);
    REP(i,H) cin >> fi[i];
    vector<vector<pair<int, int>>> alp(26);
    int sh, sw, gh, gw;
    REP(i,H) REP(j,W){
        if(fi[i][j] == 'S') sh = i, sw = j;
        else if(fi[i][j] == 'G') gh = i, gw = j;
        else if(fi[i][j] != '#' and fi[i][j] !='.'){
            alp[fi[i][j] - 'a'].emplace_back(i, j);
        }
    }
    vector<vector<int>> dist(H, vector<int>(W, INF));
    queue<pair<int, int>> q;
    q.emplace(sh, sw);
    dist[sh][sw] = 0;
    vector<bool> used(26);
    while(!q.empty()){
        auto [h, w] = q.front(); q.pop();
        if('a' <= fi[h][w] and fi[h][w] <= 'z' and !used[fi[h][w]-'a']){
            used[fi[h][w]-'a'] = true;
            for(auto [nh, nw] : alp[fi[h][w]-'a']){
                if(dist[nh][nw] != INF) continue;
                if(chmin(dist[nh][nw], dist[h][w] + 1)){
                    q.emplace(nh, nw);
                }
            }
        }
        REP(i,4){
            int nh = h + dx[i], nw = w + dy[i];
            if(nh < 0 or nh >= H or nw < 0 or nw >= W or fi[nh][nw] == '#') continue;
            if(dist[nh][nw] != INF) continue;
            if(chmin(dist[nh][nw], dist[h][w] + 1)) q.emplace(nh, nw);
        }
    }
    ll ans = dist[gh][gw];
    if(ans == INF) ans = -1;
    cout << ans << endl;
}

F - Programming Contest

半分全列挙をする
集合の半分をbit全探索で各組み合わせのごとの和を昇順ソートした配列を作る
もう半分の集合にbit全探索を行い$ T $以下になる和の最大を二分探索で求める
提出コード

void solve(){
    int N;
    ll T;
    cin >> N >> T;
    vector<ll> A(N);
    REP(i,N) cin >> A[i];
    int n2 = N / 2;
    vector<ll> v(1<<n2);
    REP(i,1<<n2){
        ll sumT = 0;
        REP(j,n2) if(i >> j & 1) sumT += A[j];
        v.emplace_back(sumT);
    }
    sort(v.begin(), v.end());
    ll ans = 0;
    dump(v);
    REP(i,1<<(N-n2)){
        ll sumT = 0;
        REP(j,N-n2) if(i >> j & 1) sumT += A[j+n2];
        dump(sumT);
        if(sumT <= T){
            ll t = *(upper_bound(v.begin(), v.end(),T - sumT) - 1);
            chmax(ans, sumT + t);
        }
    }
    cout << ans << endl;
}

AtCoder Beginner Contest 183(ABC183)

atcoder.jp

A - ReLU

そのまま式の値を出力
提出コード

void solve(){
    int x;
    cin >> x;
    if(x >= 0) cout << x << endl;
    else cout << 0 << endl;
}

B - Billiards

Dまでだと一番難しく感じた
syからy = 0までとy = 0からgyまでの間に$ gx - sx $だけ移動する
これは、sxgxsy : gyで内分する点になる
提出コード

void solve(){
    double sx, sy, gx, gy;
    cin >> sx >> sy >> gx >> gy;
    printf("%.12lf\n", sx + (gx - sx) / (sy + gy) * sy);
}

C - Travel

$ 8! $を全探索しても間に合うのでnext_permutationなどで全探索をする
提出コード

void solve(){
    ll N, K;
    cin >> N >> K;
    vector<vector<ll>> T(N, vector<ll>(N));
    REP(i,N) REP(j,N) cin >> T[i][j];
    vector<int> idx(N);
    iota(idx.begin(), idx.end(), 0);
    int ans = 0;
    do{
        if(idx[0] != 0) continue;
        ll cur = 0;
        for(int i=1;i<N;i++){
            cur += T[idx[i]][idx[i-1]];
        }
        cur += T[idx.back()][0];
        if(cur == K) ans++;
    }while(next_permutation(idx.begin(), idx.end()));
    cout << ans << endl;
}

D - Water Heater

imos法を使う
imos法で累積和を求めた後、各時刻で$ W $を超える箇所があるかをチェック
提出コード

void solve(){
    ll N, W;
    cin >> N >> W;
    vector<ll> S(N), T(N), P(N);
    vector<ll> imos(202020);
    REP(i,N){
        cin >> S[i] >> T[i] >> P[i];
        imos[S[i]] += P[i];
        imos[T[i]] -= P[i];
    }
    REP(i,202020-1) imos[i+1] += imos[i];
    string ans = "Yes";
    
    REP(i,202020) if(imos[i] > W) ans = "No";
    cout << ans << endl;
}

E - Queen on Grid

頭壊れる
各方向に対してのみ動くとするとその遷移は一つ前までの状態とそこまでの累積和になる
そのため、横、下、斜め下の3方向に対して累積和を持ちながらdpで更新していく
提出コード

void solve(){
    int H, W;
    cin >> H >> W;
    vector<string> fi(H);
    REP(i,H) cin >> fi[i];
    vector<vector<mint>> dp(H, vector<mint>(W));
    vector<vector<mint>> sum1(H+1, vector<mint>(W+1));
    auto sum2 = sum1, sum3 = sum1;
    dp[0][0] = 1;
    for(int i=0;i<H;i++) for(int j=0;j<W;j++){
        if(fi[i][j] == '#') continue;
        dp[i][j] += sum1[i][j] + sum2[i][j] + sum3[i][j];
        if(i + 1 < H) sum1[i+1][j] += dp[i][j] + sum1[i][j];
        if(j + 1 < W and fi[i][j+1] != '#') sum2[i][j+1] += dp[i][j] + sum2[i][j];
        if(i + 1 < H and j + 1 < W) sum3[i+1][j+1] = dp[i][j] + sum3[i][j];
    }
    cout << dp[H-1][W-1] << endl;
}

F - Confluence

マージテクを使う
各生徒の集合をUnionFindで管理をし、その集合の各クラスの生徒数をmapで管理をする
集合同士をmergeする際にサイズの大きい方に小さい方をmergeすればいい
提出コード

void solve(){
    int N, Q;
    cin >> N >> Q;
    vector<map<int, int>> mp(N);
    UnionFind uf(N);
    REP(i,N){
        int c;
        cin >> c;
        mp[i][c] = 1;
    }
    while(Q--){
        int q, x, y;
        cin >> q >> x >> y;
        if(q == 1){
            x--, y--;
            if(uf.issame(x, y)) continue;
            if(uf.size(x) < uf.size(y)) swap(x, y);
            for(auto &p : mp[uf.root(y)]){
                if(mp[uf.root(x)].count(p.first)) mp[uf.root(x)][p.first] += p.second;
                else mp[uf.root(x)][p.first] = p.second;
            }
            uf.unite(x, y);
        }
        else{
            --x;
            cout << mp[uf.root(x)][y] << endl;
        }
    }
}

AtCoder Beginner Contest 182(ABC182)

atcoder.jp

A - twiblr

$ B \leq 2A + 100 $ なので$ 2A + 100 - B $を出力
提出コード

void solve(){
    int A, B;
    cin >> A >> B;
    cout << max(0, 2*A+100-B) << endl;
}

B - Almost GCD

制約が小さいので$ A_i $が$2 \leq j \leq 1000 $で割り切れるならその個数をカウントしていけばいい
カウントした個数が最大の$ j $を出力
提出コード

void solve(){
    int N;
    cin >> N;
    vector<int> A(N);
    vector<int> cnt(1001);
    REP(i,N){
        cin >> A[i];
        for(int j=2;j<=1000;j++) if(A[i] % j == 0) cnt[j]++;
    }
    int ma = 0;
    int ans = 0;
    for(int i=1000;i>=2;i--){
        if(chmax(ma, cnt[i])) ans = i;
    }
    cout << ans << endl;
}

C - To 3

桁和が3の倍数なら何も消さなくていい
各桁を$ mod $ $ 3 $上で考えた時の個数と総和を求める
$ sum = 0 $の時何も消さなくていいので$ 0 $ $ sum = 1 $の時1を1つ消すか2を2つ消せるなら消せばいい
$ sum = 2 $の時1を2つ消すか2を1つ消せるなら消せばいい
提出コード

void solve(){
    ll N;
    cin >> N;
    vector<int> cnt(3);
    ll sum = 0;
    int k = 0;
    while(N > 0){
        int num = N % 10;
        num %= 3;
        cnt[num]++;
        sum += num;
        N /= 10;
        k++;
    }
    if(sum % 3 == 0) cout << 0 << endl;
    else if(sum % 3 == 1){
        if(cnt[1] >= 1 and k > 1) cout << 1 << endl;
        else if(cnt[2] >= 2 and k > 2) cout << 2 << endl;
        else cout << -1 << endl;
    }
    else{
        if(cnt[2] >= 1 and k > 1) cout << 1 << endl;
        else if(cnt[1] >= 2 and k > 2) cout << 2 << endl;
        else cout << -1 << endl;
    }
}

D - Wandering

$ A_i $の累積和を考える
各操作ごとに最終的に加算される値は決まっている
$ A_j $を見ている時、$ A_1 + A_2 + ... + A_{j-1} $を順番に足していく時、その累積和がmaxになる時点が最大となる可能性がある
そのため、累積和とそのmaxを求めながら最大になる座標を求めればいい
提出コード

void solve(){
    int N;
    cin >> N;
    vector<int> A(N);
    REP(i,N) cin >> A[i];
    ll cum = 0, cur = 0;
    ll ans = 0, ma = 0;
    REP(i,N){
        chmax(ans, cur + ma);
        cur += cum + A[i];
        cum += A[i];
        chmax(ma, cum);;
        chmax(ans, cur);
        // dumps(ans, cur, ma);
    }
    cout << ans << endl;
}

E - Akari

各マスにたいして、ランプからの光が上下左右のどの方向から来たかを状態に持ってBFSをする
そうすると、ある1マスに対して最大4方向からの状態しかないので最大でも$ 4HW $で状態数が抑えられる
提出コード

void solve(){
    int H, W, N, M;
    cin >> H >> W >> N >> M;
    vector<vector<bool>> fi(H, vector<bool>(W, true));
    queue<tuple<int, int, int>> q;
    vector<int> A(N), B(N);
    vector<int> C(M), D(M);
    REP(i,N){
        cin >> A[i] >> B[i];
        --A[i], --B[i];
    }
    REP(i,M){
        cin >> C[i] >> D[i];
        --C[i], --D[i];
        fi[C[i]][D[i]] = false;
    }
    vector<vector<vector<bool>>> used(4, vector<vector<bool>>(H, vector<bool>(W)));
    REP(i,N){
        used[0][A[i]][B[i]] = true;
        used[1][A[i]][B[i]] = true;
        used[2][A[i]][B[i]] = true;
        used[3][A[i]][B[i]] = true;
        REP(d,4){
            int nh = A[i] + dx[d], nw = B[i] + dy[d];
            if(nh < 0 or nh >= H or nw < 0 or nw >= W or !fi[nh][nw]) continue;
            q.emplace(d, nh, nw);
        }
    }
    while(!q.empty()){
        auto [dir, h, w] = q.front(); q.pop();
        if(used[dir][h][w]) continue;
        used[dir][h][w] = true;
        int nh = h + dx[dir], nw = w + dy[dir];
        if(nh < 0 or nh >= H or nw < 0 or nw >= W or !fi[nh][nw]) continue;
        if(used[dir][nh][nw]) continue;
        q.emplace(dir, nh, nw);
    }
    int ans = 0;
    REP(i,H) REP(j,W){
        bool ok = false;
        REP(k,4) if(used[k][i][j]) ok = true;
        if(ok) ans++;
    }
    cout << ans << endl;
}

AtCoder Beginner Contest 181(ABC181)

atcoder.jp

A - Heavy Rotation

$ n $ の偶奇で場合分け
提出コード

void solve(){
    int n;
    cin >> n;
    if(n & 1) cout << "Black" << endl;
    else cout << "White" << endl;
}

B - Trapezoid Sum

$ A_i , B_i $の和は等差数列の和で$ \frac{(B_i - A_i + 1) \times (A_i + B_i)}{2} $で求められる
よって各$ A_i, B_i $の和の総和を求めればいい
提出コード

void solve(){
    int N;
    cin >> N;
    ll sum = 0;
    REP(i,N){
        ll a, b;
        cin >> a >> b;
        sum += (b - a + 1) * (a + b) / 2;
    }
    cout << sum << endl;
}

C - Collinearity

3点が一直線上にあるかどうかは直線$ (i, j) $と直線$ (i, k) $が同じ傾きかどうかで判定できる
よって3点$(i, j, k) $を全探索して傾きが一致するものがあるかどうかを判定すればいい
直線$ (i, j) $の傾きを$ \frac{y_1}{x_1} $、直線$ (i, k) $の傾きを$ \frac{y_2}{x_2} $とする
そうすると、分母を払うと$ x_1 y_2 = x_2 y_1 $で判定ができて実装が楽になる
提出コード

void solve(){
    int N;
    cin >> N;
    vector<int> x(N), y(N);
    REP(i,N) cin >> x[i] >> y[i];
    bool ok = false;
    REP(i,N) for(int j=i+1;j<N;j++) for(int k=j+1;k<N;k++){
        int x1 = x[i] - x[j], y1 = y[i] - y[j];
        int x2 = x[i] - x[k], y2 = y[i] - y[k];
        int g1 = gcd(abs(x1), abs(y1));
        int g2 = gcd(abs(x2), abs(y2));
        x1 /= g1, y1 /= g1, x2 /= g2, y2 /= g2;
        if(x1 == 0) y1 = 1;
        if(y1 == 0) x1 = 1;
        if(x1 < 0) x1 *= -1, y1 *= -1;
        if(x2 == 0) y2 = 1;
        if(y2 == 0) x2 = 1;
        if(x2 < 0) x2 *= -1, y2 *= -1;
        if(x1 == x2 and y1 == y2) ok = true;
    }
    if(ok) cout << "Yes" << endl;
    else cout << "No" << endl;
}

D - Hachi

$ |S| \geq 3 $のとき、下3桁で8の倍数が作れれば良い
そのため、$ S $の1~9の個数をカウントしておき、3桁の8の倍数を全探索し、これが作れれば良い
$ |S| = 1 $と$ |S| = 2 $のときは場合分けする必要がある
提出コード

void solve(){
    string s;
    cin >> s;
    vector<int> cnt(10);
    REP(i,s.size()){
        cnt[s[i]-'0']++;
    }
    if(s.size() == 1){
        if(s == "8") cout << "Yes" << endl;
        else cout << "No" << endl;
        return;
    }
    if(s.size() <= 2){
        for(int i=16;i<100;i+=8){
            int t = i % 10;
            int u = i / 10;
            if((s[0] - '0' == t and s[1] - '0' == u) or(s[0] - '0' == u and s[1] - '0' == t)){
                cout << "Yes" << endl;
                return;
            }
        }
        cout << "No" << endl;
        return;
    }
    for(int i=104;i<=1000;i+=8){
        bool ok = true;
        int cur = i;
        vector<int> tmp(10);
        while(cur > 0){
            tmp[cur%10]++;
            cur /= 10;
        }
        REP(i,10) if(cnt[i] < tmp[i]) ok = false;
        if(ok){
            cout << "Yes" << endl;
            // dump(i);
            return;
        }
    }
    cout << "No" << endl;
}

E - Transformable Teacher

添字で頭が壊れた
$ W_i $を$ H $に追加したものをソートして考えると昇順にペアを作っていけばいい
そのため、$ W_i $をどこに追加するかは二分探索で求めることができる
隣り合う要素の和の累積和を取って上げると、$ W_i $を追加してできるペアとその前後の総和の最小値を求めればいい
$ W_i $を追加した時にその前後で累積和の偶奇が変わるので注意
提出コード

void solve(){
    int N, M;
    cin >> N >> M;
    vector<ll> H(N), W(M);
    REP(i,N) cin >> H[i];
    REP(i,M) cin >> W[i];
    sort(H.begin(), H.end());
    vector<ll> cum_odd(N+1);
    vector<ll> cum_even(N+1);
    REP(i,N-1){
        if(i & 1){
            cum_even[i+1] = cum_even[i] + H[i+1] - H[i];
            cum_odd[i+1] = cum_odd[i];
        }
        else{
            cum_even[i+1] = cum_even[i];
            cum_odd[i+1] = cum_odd[i] + H[i+1] - H[i];
        }
    }
    ll ans = LINF;
    REP(i,M){
        {
            int idx = upper_bound(H.begin(), H.end(), W[i]) - H.begin();
            if(idx & 1){
                if(idx == N) chmin(ans, abs(W[i] - H[idx-1]) + cum_odd[idx-1]);
                else chmin(ans, abs(W[i] - H[idx-1]) + cum_odd[idx-1] + cum_even[N-1] - cum_even[idx]);
            }
            else{
                if(idx == 0) chmin(ans, H[idx] - W[i] + cum_even[N-1] - cum_even[idx]);
                else chmin(ans, H[idx] - W[i] + cum_odd[idx-1] + cum_even[N-1] - cum_even[idx]);
            }
        }
    }
    cout << ans << endl;
}

F - Silver Woods

これすき、解説ACした
$ y = 100, y = -100 $の二直線と各頂点を結んで二直線の間に線(直線じゃなくても良い)が結ばれたとき、各頂点間の距離で最大のところを円の直径とすれば円はゴールまで通る事ができる
よって二直線も頂点とみなし、各頂点間の距離が小さい順にUnionFindなどで頂点集合をmergeし、$y = 100 $と$ y = -100 $が同じ連結成分となった時、最大の距離の半分が求めたい$ r $となる
提出コード

void solve(){
    int N;
    cin >> N;
    vector<int> x(N), y(N);
    REP(i,N) cin >> x[i] >> y[i];
    vector<tuple<double, int, int>> vt;
    int y1 = N, y2 = N+1;
    REP(i,N){
        vt.emplace_back(y[i] + 100, i, y1);
        vt.emplace_back(100 - y[i], i, y2);
        for(int j=i+1;j<N;j++){
            vt.emplace_back(hypot(x[i]-x[j], y[i]-y[j]), i, j);
        }
    }
    sort(vt.begin(), vt.end());
    UnionFind uf(N+2);
    for(auto [d, i, j] : vt){
        uf.unite(i, j);
        if(uf.issame(y1, y2)){
            printf("%.12lf\n", d / 2.0);
            return;
        }
    }
}

AtCoder Regular Contest 107(ARC107)

atcoder.jp

A - Simple Math

式変形を行うと
$ \sum_{a=1}^{A} \sum_{b=1}^{B} \sum_{c=1}^{C} abc $
$ = \sum_{a=1}^{A} a \sum_{b=1}^{B} b \sum_{c=1}^{C} c $
$ = ( \frac{A * (1 + A)}{2} \times \frac{B * (1 + B)}{2} \times \frac{C * (1 + C)}{2} )$
となるのでそれぞれの等差数列の和の積が答え
提出コード

void solve(){
    ll A, B, C;
    cin >> A >> B >> C;
    mint a = A, b = B, c = C;
    mint ans = a * (a + 1) / 2 * b * (b + 1) / 2 * c* (c + 1) / 2;
    cout << ans << endl;
}

B - Quadruple

$ a + b - (c + d) = K $となるので$ a + b $ と$ c + d $に分けて考える(Kが負のときは入れ替えて考えればいい)
この時、$ a+ b $ の取る範囲を考えると $ c + d \geq 2$より$ x = K + 2 $が最小で$ 2 \times N $が最大となる
よって$ a + b = x $として$ 2 + K \leq x \leq 2 \times N $を全探索すればいい
この時$ a + b = x $となる$ ( a, b) $の組み合わせの数は$ min(x-1, 2 \times N - x + 1) $となる
$ c + d = y = x - K $の場合も同様に考えればいい
よって各$ x $ について $ min(x-1, 2 \times N - x + 1) \times min(y-1, 2 \times N - y + 1) $ の積が答え
提出コード

void solve(){
    int N, K;
    cin >> N >> K;
    ll ans = 0;
    K = abs(K);
    for(int i=K+2;i<=2*N;i++){
        ll x = i;
        ll y = i - K;
        x = min(x-1, 2*N+1-x);
        y = min(y-1, 2*N+1-y);
        ans += x * y;
    }
    cout << ans << endl;
}

C - Shuffle Permutation

行と行をswapした場合、各列には影響を及ぼさない
列と列をswapした場合も同様で、行方向と列方向を独立に考えても良い
行$ (i, j) $がswapできて$ (i, k) $がswapできる時、$ i, j, k $は任意の位置にswapできる
そのため、swapできる行をUnionFindなどで集合として管理をする
各集合において、集合の要素の任意の位置にswapで移動することができるので各集合のサイズの階乗をかけ合わせていけばいい
提出コード

void solve(){
    int n, K;
    cin >> n >> K;
    vector<vector<int>> a(n, vector<int>(n));
    REP(i,n) REP(j,n) cin >> a[i][j];
    UnionFind uf1(n), uf2(n);
    REP(i,n) REP(j,n) if(i != j){
        bool ok = true;
        REP(k,n){
            if(a[i][k] + a[j][k] > K){
                ok = false;
            }
        }
        if(ok) uf1.unite(i, j);
    }
    REP(i,n) REP(j,n) if(i != j){
        bool ok = true;
        REP(k,n) if(a[k][i] + a[k][j] > K) ok = false;
        if(ok) uf2.unite(i, j);
    }
    set<int> st1, st2;
    REP(i,n){
        st1.insert(uf1.root(i));
        st2.insert(uf2.root(i));
    }
    mint ans1 = 1;
    mint ans2 = 1;
    for(auto x : st1) ans1 *= bc.fact(uf1.size(x));
    for(auto x : st2) ans2 *= bc.fact(uf2.size(x));
    cout << ans1 * ans2 << endl;
}

D - Number of Multisets

想定解でupsolve
$ dp[n][k] := $ $ n $個の要素を取った時の総和が$ k $の時の場合の数としてDPをする
1を取る時の遷移は$ 1, \frac{1}{2}, \frac{1}{4}, ... $ から $ n - 1 $個取って、その総和が$ k - 1 $である場合の数となるので、に $ dp[n][k] += dp[n - 1][k - 1] $となる
1を取らない時の場合、$ \frac{1}{2}, \frac{1}{4}, ... $から$ n $個取って、その総和が$ k $である場合の数となる
ここで、全ての値を2倍にすると$ 1, \frac{1}{2}, \frac{1}{4}, ... $から$ n $個取って、その総和が$ 2k $となる場合の数と等しくなるので$ dp[n][k] += dp[n][2k] $となる
よって上記のようにDPをした時の$ dp[N][K] $が答え
初期化として$ dp[0][0] = 1 $
$ n \lt k $ のとき条件を満たさないので$ dp[n][k] = 0 (n \lt k) $
$ n \gt 0 , k = 0 $のときも同様に条件を満たさないので$ dp[n][k] = 0 (n \gt 0 , k = 0) $
となる 提出コード

void solve(){
    int n, k;
    cin >> n >> k;
    vector<vector<mint>> dp(n+10, vector<mint>(n+10));
    vector<vector<bool>> used(n+10, vector<bool>(n+10));
    auto dfs = [&](auto && self, int N, int K) -> mint{
        if(N == 0 and K == 0) return 1;
        if(K > N) return 0;
        if(N > 0 and K == 0) return dp[N][K] = 0;
        if(used[N][K]) return dp[N][K];
        used[N][K] = true;
        mint res = self(self, N-1, K-1) + self(self, N, 2*K);
        return dp[N][K] = res;
    };
    cout << dfs(dfs, n, k) << endl;
}