Toy と帽子と ADP BE

主にプログラミングに関わる話をゆるくエモくやっていきます

B. Same Parity Summands (Codeforces Round #640 Div. 4)

問題

https://codeforces.com/contest/1352/problem/B

問題概要

整数nとkが与えられる。nをk個の非負整数の和として表した時、k個すべてを奇数またはk個すべてを偶数とする方法はあるか答えよ。ある場合は一例を示せ。

考察

まず、nよりkのほうが大きいときは分割できないので構築不可能です。

あとは1 1 1 ... n - (k - 1)としてn - (k - 1)が奇数になるか、2 2 2 ... n - ((k - 1) * 2)としてn - ((k - 1) * 2)が(正の)偶数になるかを満たせば構築可能で、どちらも満たさない場合は構築不可能となります。

#include <bits/stdc++.h>
using namespace std;
 
int main() {
    int t;
    cin >> t;
    while (t--) {
        int n, k;
        cin >> n >> k;
        if (n < k) {
            cout << "NO" << endl;
            continue;
        }
        if ((n - (k - 1)) % 2 == 1) {
            cout << "YES" << endl;
            for (int i = 0; i < k - 1; i++) cout << 1 << ' ';
            cout << n - (k - 1) << endl;
            continue;
        }
        if (n - ((k - 1) * 2) > 0 and (n - ((k - 1) * 2)) % 2 == 0) {
            cout << "YES" << endl;
            for (int i = 0; i < k - 1; i++) cout << 2 << ' ';
            cout << n - ((k - 1) * 2) << endl;
            continue;
        }
        cout << "NO" << endl;
    }
}

個人的反省

本番では場合分けを頑張りすぎてグダグダになってしまいました。

実装で場合分けが複雑になりすぎてるときは一旦算数の考察に戻った方がよいです。そして、導き出した条件を素直に実装してみましょう。

ということをしょっちゅう言っているような気がしますがなかなか改善されません・・・。

A. Sum of Round Numbers (Codeforces Round #640 Div. 4)

記念すべきこどふぉDiv. 4の一問目!

問題

https://codeforces.com/contest/1352/problem/A

問題概要

整数nが与えられるので、それを丸めた数の和として表現せよ。その際、項の数は最小になるようにせよ。

考察

例えば1234なら1000200304に分けろということです。

やりかたはいろいろありそうですが、自分はコンテストでは入力を文字列として取って、各桁の数に対して適切な数だけ0を付加して作っていきました。0の桁は対象外とする必要があります。

#include <bits/stdc++.h>
using namespace std;
 
int main() {
    int t;
    cin >> t;
    while (t--) {
        string s;
        cin >> s;
        int n = s.size();
        vector<string> ans;
        for (int i = 0; i < n; i++) {
            if (s[i] != '0') {
                ans.emplace_back(s[i] + string(n - 1 - i, '0'));
            }
        }
        int sz = ans.size();
        cout << sz << endl;
        for (int i = 0; i < sz; i++) cout << ans[i] << (i == sz - 1 ? '\n' : ' ');
    }
}

別解

素直に数値として考えるなら、剰余を取って引いて、を桁を上がりつつ繰り返すというやり方が一例となります。

#include <bits/stdc++.h>
using namespace std;
 
int main() {
    int t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        vector<int> ans;
        int d = 1;
        while (d <= n) {
            d *= 10;
            if (n % d != 0) ans.emplace_back(n % d);
            n -= n % d;
        }
        int sz = ans.size();
        cout << sz << endl;
        for (int i = 0; i < sz; i++) cout << ans[i] << (i == sz - 1 ? '\n' : ' ');
    }
}

感想

正直AtCoderのABCのBくらい簡単な問題が来ると予想していたので、ちょっと驚きました。この問題ならDiv. 3やDiv. 2で出ても違和感ないのでは?

AtCoder Beginner Contest 166

5完2WA。

久々の5完なのに緑パフォとか、世間の風は冷たい・・・。

各問題

A - A?C

入力がABCならARCを、ARCならABCを出力です。

B - Trick or Treat

N人のすぬけ君のためにintの配列を用意して、お菓子毎に持っているすぬけ君のカウントを上げていけば、最終的にカウントが0のままのすぬけ君がいたずらを受けるので、それを数え上げて出力します。

B問題にしてはちょっと骨のある問題だった気が。

C - Peaks

1本の道単位でしか高さをチェックする必要がないので、各道ごとに低い方をNGとしていって、最終的にNGにならなかった展望台の数が答えです。なお、高さが同じ場合は両方NGとします。

私はif - else if - else としたかったところを、if - if -else としてしまって1WA出してしまいました。最近こういうミスが多い・・。

D - I hate Factorization

A^5 - B^5 = Xを式変形してA^5 = X + B^5とすることで、X + B^5もある整数の5乗であることがわかります。

前準備として、1から順に5乗の数を作って元の数とべき数の組で保存しておき、Bを適切な範囲で全探索してX + B^5が保存しておいたべき数に一致すればそれは満たす組の片割れであることが確定するので、Bと保存しておいた元の数を出力すればよいです。

E - This Message Will Self-Destruct in 5s

入力A_iにたいして、B_i = A_i - iとします(0-indexed)。B_iはmapで集計しておきます。

あらかじめインデックスを引いているので、i<jとなるjに対してA_i + B_j + i = 0となったものが条件を満たすペアとなり、この数はあらかじめ前処理しているので、集計処理は全体でO(N)で済みます。

あ、なおmapからはi<jを満たせるように、都度A_iより生成されたB_iのカウントを引いていく必要があります。

F - Three Variables Game

久しぶりにF問題を読んだ気がしますが、残り時間も少なく全く何も浮かんできませんでした。

まとめ

いっときのスランプ状態は乗り越えて、上向きにはなっている気がします。あとはつまらないミスで時間を溶かすのをどうにかしないといけませんね。

f:id:mdstoy:20200503230410p:plain

AtCoder Beginner Contest 165

4完1WA。

いやほんまにw

各問題

A - We Love Golf

O(1)の解法でもいいんですが、つまらないミスをしてもいやだし、何も考えず全探索書くほうが速いし(コンテストだから)、というわけで、AからBまでループを回してKの倍数であるかどうかをチェックしました。

B - 1%

入力例2を見ればわかるとおり、10^18でも3760年で到達してしまうので、愚直にシミュレーションすればよいだけです。最大でも3760回で終わるのです。(サンプル優しい)

C - Many Requirements

制約を見ても、C問題であることを考えても(メタい)、これは全探索できると考えられます。

自分はベタな幅優先探索で実装しました。メモ化は必要ですし、A[i]>A[i+1]の枝はもちろん刈ります。

(とかいって、実はBFSかDFSでいいやん、というのに気づくまでに1時間使ってたりします・・・。)

D - Floor Function

実験すると周期がB(ただし0から始まる)かつ単調増加であることがわかります。(おい

なので、B <= Nのときはx = B - 1として計算し、B > Nのときはx = Nとして計算すればよいです。

一箇所だけ変数をうっかりintにしてしまい1WA。ああ・・・。

E - Rotation Matching

サンプル2がほぼほぼ答えなんですが、偶奇によってうまくいかないことはわかりました。ただ、どうすればいいかはわかりませんでした。

F - LIS on Tree

見てません。

まとめ

全探索できることは想像ついても、実装方針で悩みすぎたのはよくないですね。

まあ、久々に書いたBFSをバグらせなかったのでよしとしましょうか。

f:id:mdstoy:20200502231744p:plain

AtCoder Beginner Contest 164

3完1WA。

各問題

A - Sheep and Wolves

S <= Wが成り立つならunsafe、そうでないならsafeです。

B - Battle

シミュレーションすればよいです。先に体力が0になった方の負けです。

私はa <= 0とすべきところをa == 0としてしまい1WAを出した上に、それに10分以上気づきませんでして、それだけでパフォーマンスが数百溶けたらしいです。

C - gacha

出てきた文字列を、重複を許さないコレクション(c++ならsetとか)にぶち込んで、数を数えれば終了です。

D - Multiple of 2019

計算量を削る方法がわからずじまいでした。2019 = 3 * 673なので、3の倍数でなければスキップとかやりましたけど焼け石に水でした。

EとF

みてません。

まとめ

<===であることに10分気づかなかった」というのが、パフォーマンスを数百点も下げるような罪なんですかね? いやまあ拙速を尊ぶあまりWAを出してちゃ世話ないよねっていう話ではあるんですけど。 はぁ・・・。

Dが難問の時に限ってこういうことやらかしちゃうからなぁ・・・。いや、D通せよっていう話でもありますけどね。

f:id:mdstoy:20200426230048p:plain

Re: D. Nastya and Scoreboard (Codeforces Round #637 Div. 2)

mdstoy.hatenablog.com

この記事の続きです。ちゃんと想定解のDPで解き直したので、それの解説というか、おぼえがきです。

問題

https://codeforces.com/contest/1341/problem/D

問題概要

7セグがn個並んでいて、点灯状況が与えられる。しかし、k個のセグメントが故障している。

k個の故障箇所を直した時に表現できる可能性のある数値のうちで、最大のものを答えよ。ただし、「故障」とは点灯すべきセグメントが点灯していない状況を指す。(本来消灯すべきセグメントが点灯しているという状況は考慮しない) ただし、いかなる組み合わせでも数値として表現不可能である場合は-1を回答とせよ。

考察

制約(n <= 2000)や問題の性質から察するに、こういうのはだいだいDPで解けそうだとなり、実際解けます。ただし、何も考えずにDP配列を作ったり漸化式を作ると失敗します。(しました)

失敗その1

dp[i][j] = i文字目までにj個のセグメントを修正した時に作れる最大の数値(文字列)

とすればいけそうな気がします。実際いけます(多分)。メモリ空間が無限に与えられていれば!

というわけで、そんな巨大な文字列配列を作るとMLEで死にます。(死にました)

MLEになるコード例 - > Submission #78030083 - Codeforces

失敗その2

ここでわからなくなったので、仕方なくEditorialを開きますと、Let dp[i][j]=true...とか書いてあるので、DP配列はboolで持って遷移可能範囲を先に決めてから、あとで復元すればよいということがわかります。(わかりました)

ここで気をつけないといけないのは、DP配列を埋めるのは後ろから(この問題だと右側から)がよいということです。前から埋めてしまうと、きっちりkを使い切ったかどうかの判断ができなくなる(やりにくくなる?)からです。

前からやろうとして失敗した例 - >Submission #78039757 - Codeforces

完成

というわけで、完成したのがこちらです - >Submission #78041529 - Codeforces

まとめ

メモリ使用量とか、DP配列の構築法とかいろいろ勉強になりました。(今まで知らんかったんかい、というツッコミはさておいて)

D. Nastya and Scoreboard (Codeforces Round #637 Div. 2)

注意:以下の解説は想定解ではありません。しかも、コンテストではシステムテストを通ったのに、今投げるとTLEで落ちます。(えー

想定解はDPですのでDPで解きましょう。

この記事は自分のための備忘録として残しておくものです。

(2020-04-26追記)DPで解き直したので、別の記事に書きました。(追記ここまで)

問題

https://codeforces.com/contest/1341/problem/D

問題概要

7セグがn個並んでいて、点灯状況が与えられる。しかし、k個のセグメントが故障している。

k個の故障箇所を直した時に表現できる可能性のある数値のうちで、最大のものを答えよ。ただし、「故障」とは点灯すべきセグメントが点灯していない状況を指す。(本来消灯すべきセグメントが点灯しているという状況は考慮しない) ただし、いかなる組み合わせでも数値として表現不可能である場合は-1を回答とせよ。

考察

制約や問題の性質を見て、あ、これはDPだなとは思ったのですが、実戦ではまあ上から貪欲でもいけるんじゃない?と思って突っ走ってしまいました。以下、その実戦での解法となります。

概要

概要としては単純で、DFSで大きい数字から順に構築していって、構築できたらそれを答えとして終了、というだけです。ただし、答えが後ろの方にあるとTLEしてしまいますので、枝狩りする必要があります。

私は、各7セグに対して追加すべきセグメントの最小値と最大値を累積和で保存して、kの残りがその範囲内で収まっていない場合即NGとしました。

例えば残り3つの状況でkの残りが0のとき、残り3つのうち一つでも追加のセグメントが必要であれば構築不可能です。また、残り3つの状況でkの残りがいくつかあるのに、残り3つすべてが追加のセグメントを必要としない場合などもkを使い切っての構築は不可能です。

回答例

注意:以下の回答は、現在TLEで落ちます

コンテスト時点ではなかったテストケースが追加されているようです。

#include <bits/stdc++.h>
using namespace std;
 
int main() {
    // 9から降順に並べる(貪欲にチェックするため)
    vector<bitset<7>> b{
        bitset<7>("1111011"),
        bitset<7>("1111111"),
        bitset<7>("1010010"),
        bitset<7>("1101111"),
        bitset<7>("1101011"),
        bitset<7>("0111010"),
        bitset<7>("1011011"),
        bitset<7>("1011101"),
        bitset<7>("0010010"),
        bitset<7>("1110111")
    };
 
    int n, k;
    cin >> n >> k;
    vector<bitset<7>> d(n);
    // 必要なセグメントの最小値
    vector<int> mn(n + 1, 0);
    // 必要なセグメントの最大値
    vector<int> mx(n + 1, 0);
    string s;
    for (int i = 0; i < n; i++) {
        cin >> s;
        d[i] = bitset<7>(s);
        int mnn = 8;
        int mxx = -1;
        for (int j = 0; j < 10; j++) {
            // 対象の数字が構築可能かどうかをまずチェック
            if (((d[i] | b[j]) ^ b[j]).any()) continue;
            // 対象の数字を構築するのにあといくつセグメントが必要か
            int e = b[j].count() - (d[i] & b[j]).count();
            mnn = min(mnn, e);
            mxx = max(mxx, e);
        }
        mn[i + 1] = mn[i] + mnn;
        mx[i + 1] = mx[i] + mxx;
    }
 
    function<string(string, int, int)> dfs = [&](string x, int c, int r) {
        if (c == n) {
            // 最後まで到達した時ちょうどkを使い切っていないといけない
            if (r == 0) return x;
            else return string("-1");
        }
        // 修理するセグメントの残りが範囲外ならNG
        if (r < mn[n] - mn[c] or r > mx[n] - mx[c]) return string("-1");

        string ret = "-1";
        for (int i = 0; i < 10; i++) {
            // 対象の数字が構築可能かどうかをまずチェック
            if (((d[c] | b[i]) ^ b[i]).any()) continue;
            // k の消費数
            int e = b[i].count() - (d[c] & b[i]).count();
            // k が足りないなら不採用
            if (r - e < 0) continue;
            // 構築できているなら次の桁へ
            ret = max(ret, dfs(x + to_string(9 - i), c + 1, r - e));
            // 最後まで構築できているなら即終了
            if (ret != "-1") break;
        }
 
        return ret;
    };
 
    cout << dfs("", 0, k) << endl;
}

反省

こういうのを枝狩りで頑張ろうとするとWAが増えがちなので(実際6WA出した)(いやそれ以前にTLE解ですけど)、DPが見えてるんなら素直にDPで解きましょう・・・。

こんなのでHighestを大幅更新してしまって、申し訳ない気持ちと通せば勝ちなんだよという気持ちが混じり合って複雑な心境です・・・。