Open79

競技プログラムの覚書

hirohisohirohiso

点の回転と移動

        var _x1 = (Math.cos(dth) * _x) - (Math.sin(dth) * _y) + cx;
        var _y1 = (Math.sin(dth) * _x) + (Math.cos(dth) * _y) + cy;
hirohisohirohiso

for文を使い、空白区切りで出力する時

i < n ? " " : ""

で最後に空白出さないようにできる

hirohisohirohiso

Stringの配列をcharのストリームにしたい

        String[] strArray = {"Hello", "World"};

        Stream<Character> stream = Arrays.stream(strArray)
                .flatMap(str -> str.chars().mapToObj(c -> (char) c));

String.chars()でcharのIntStreamが出てくる

hirohisohirohiso

確率問題でも母数が10^5とかの場合は、数えあげでシンプルに解ける可能性がある

hirohisohirohiso

プリミティブ配列をColletionにaddAllする

        var set = new HashSet<>();
        set.addAll(Arrays.stream(An).boxed().collect(Collectors.toList()));

boxedでボクシング変換できる

hirohisohirohiso

集合Sの部分集合が与えられた時に、含まれていない物を出す。

Setに集合Sの要素を入力し、部分集合の要素を削除していけば、
Setに残ったものが答えになる

hirohisohirohiso

プリミティブ配列をGroupByしてカウント. boxed()がみそ

        var An = new int[]{1,2,2,2,3,3,3,4,5,6};

        var map = Arrays.stream(An).boxed().collect(Collectors.groupingBy(
                i -> i,Collectors.counting()
        ));
        for(var entry : map.entrySet()){
            pw.println(entry.getKey() + ":" + entry.getValue());
        }
1:1
2:3
3:3
4:1
5:1
6:1
hirohisohirohiso

Collectionsを利用したlistのsortとバイナリサーチ

        var list = new LinkedList<Integer>();
        list.add(1);
        list.add(5);
        list.add(2);
        list.add(3);
        list.add(1);
        Collections.sort(list);
        Collections.binarySearch(list,3);
        var list2 = new LinkedList<Pair>();
        list2.add(new Pair(3, 2));
        list2.add(new Pair(5, -2));
        list2.add(new Pair(1, 0));
        list2.add(new Pair(5, 2));
        list2.add(new Pair(2, 8));

        Comparator<Pair> comparator = Comparator.comparingInt(p -> p.b);
        Collections.sort(list2, comparator);
        Collections.binarySearch(list2, new Pair(1, 0), comparator);
hirohisohirohiso

よく忘れるComparatorの定義の例。
ジェネリクスはメソッドの前.
staticメソッドの場合は、メソッドの前に「クラス名.<型>」を書く。

var comparator = Comparator.<Pair>comparingInt(p -> p.b).reversed().thenComparingInt(p -> p.a);
hirohisohirohiso

mapのmergeはたまに使うかも

map.merge(key, msg, String::concat);
map.merge(key,valuew, Long::sum);
hirohisohirohiso

文字列の判定は普通に正規表現が一番早い・・・

String s = in.next();
        if (s.matches("[A-Z][1-9][0-9]{5}[A-Z]")) {
            out.println("Yes");
        } else {
            out.println("No");
        }
hirohisohirohiso

streamで条件を満たす数値をサクッと取得する。

        var N = fs.ni();
        var T = fs.ni();
        var cn = new int[N];
        var tn = new int[N];
        for (int i = 0; i < N; i++) {
            cn[i] = fs.ni();
            tn[i] = fs.ni();
        }
        var ans = IntStream.range(0,N).filter(i -> tn[i] <= T).map(i -> cn[i]).min();
        pw.println(ans.isPresent()?ans.getAsInt():"TLE");
hirohisohirohiso

N桁のK進数全探索(ABC 015より

        var N = fs.ni();
        var K = fs.ni();

        var ptn = 1;
        for (int i = 0; i < N; i++) {
            ptn *= K;
        }
        for (int i = 0; i < ptn; i++) {
            var t = i;
            for (int j = 0; j < N; j++) {
                var m = t % K; //j桁目の数値がm
                t /= K;
            }
        }
hirohisohirohiso

true/falseのdp[i]は,iをsetとかに格納すれば、より高速に解けるんでは?

dp[i] = true みたいなのを、 set.add[i]にする

hirohisohirohiso

doubleの最大値、最小値を用いた比較は誤差で誤判定になる可能性があるので、ちょっと盛った方が良い

        var EPS = 1e-7;
        var dp = new double[N + 1][N + 1];
        for (int i = 0; i < N; i++) {
            Arrays.fill(dp[i], Double.MIN_VALUE);
        }
        dp[i + 1][j] = Math.max(dp[i][j], dp[i + 1][j] + EPS);
hirohisohirohiso

Double.MIN_VALUEの意味を勘違いしていた。正の値の最小表現なのか・・・
マイナスの無限大は
Double.NEGATIVE_INFINITYで表す

hirohisohirohiso

コンテスト後に拾ってきた気になるワード

順列列挙、組み合わせ列挙
マージテク

hirohisohirohiso

答えに影響のない値を入力値に加えることで、
if文が存在しないようなロジックが作れればシンプルに書ける。

*配列を2つづつ処理していく場合などに、奇数個に0を加えて偶数にするなど(奇数個の条件削除)

hirohisohirohiso

bit演算。
1の数を数えるのはIntegerの組み込み使うんがよい

        var N = fs.ni();
        var M = fs.ni();

        var set = (1 << 31) - 1;
        for (int i = 0; i < N; i++) {
            var K = fs.ni();
            var bit = 0;
            for (int j = 0; j < K; j++) {
                var a = fs.ni();
                bit |= 1 << a;
            }
            set &= bit;
        }
        pw.println(Integer.bitCount(set));
hirohisohirohiso

https://atcoder.jp/contests/abc260/tasks/abc260_b

ソートしまくる解法

        Arrays.sort(AB, Comparator.<int[]>comparingInt(i -> i[1]).thenComparingInt(i -> -i[0]));
        Arrays.sort(AB, 0, N - X, Comparator.<int[]>comparingInt(i -> i[2]).thenComparingInt(i -> -i[0]));
        Arrays.sort(AB, 0, N - (X + Y), Comparator.<int[]>comparingInt(i -> i[2] + i[1]).thenComparingInt(i -> -i[0]));
        Arrays.sort(AB, N  - (X + Y + Z ), N, Comparator.comparingInt(i -> i[0]));
        for (int i = N  - (X + Y + Z ); i < N; i++) {
            pw.println(AB[i][0]);
        }
hirohisohirohiso

二部グラフ判定、二部マッチング
最小費用流
Zindex,SA,ロリは
Grandy数
点素パス、辺素パス

hirohisohirohiso

https://atcoder.jp/contests/arc123/editorial/2256

ソートした後に、前から順に貪欲に見ていくであっていた。(ただしいかは説明できない)

A1を選択すると、Bj <= A1となる全ての要素は選択されることがない。
A1 < Bj となるうちで一番小さい要素をB1とする。

同様の考え方で Ck < B1となる全ての要素は選択されることがない。

そうやって選ばれたA1,B1,C1は解の一つ

hirohisohirohiso

ある最適解を考えた時に、
解を最小値/最大値で入れ替えても条件を満たすような時、
貪欲法が適用できる・・・?

hirohisohirohiso

https://atcoder.jp/contests/abc273/editorial/5018

試した解法
Anからsetを作り並び替え、An[i]ごとにその数値以上の数の種類を計算、カウント
最後にKごとにカウント値を出力
(シミュレーション的)

解説の解法
An[i] = An[j]  とすると(i != j)
その数値以上の数の種類は固定。なので同時に求めることができる。

hirohisohirohiso

bit全探索スニペット化したい

        var bit = 0;
        while(bit < (1 << M)){
            for (int i = 0; i < M; i++) {
                if(((bit >> i) & 1) == 1){
                }
            }
hirohisohirohiso

ビビらないコード

    private static void solve(PrintWriter pw, FastScanner fs) {
        //==================
        var R = fs.nl();
        var X = fs.nl();
        var Y = fs.nl();
        var DD = X * X + Y * Y;

        if (DD < R * R) {
            pw.println(2);
            return;
        }

        var ans = (long) Math.ceil(Math.sqrt(DD) / R);
        pw.println(ans);
    }
hirohisohirohiso

転倒数のコードサンプル

        var S = fs.n();
        var bit = new IntFenwickTree(S.length());
        var map = new HashMap<Character, Integer>();
        map.put('a', 1);
        map.put('t', 2);
        map.put('c', 3);
        map.put('o', 4);
        map.put('d', 5);
        map.put('e', 6);
        map.put('r', 7);

        var ans = 0;
        for (int i = 0; i < S.length(); i++) {
            var j = map.get(S.charAt(i));
            ans += i - bit.sum(j);
            bit.update(j, 1);
        }
        pw.println(ans);
hirohisohirohiso

https://atcoder.jp/contests/abc106/tasks/abc106_c

先頭が1なら2文字目、そうじゃないなら1文字目で最初は考えたが、

先頭から1がN文字連続し、K < Nだった場合は1となる。
そのためK文字目まで1が連続していないかを確認し、初めて1じゃない数が来た場合はその数が答えになる。
ただしKがとても大きいので、 KとSの文字列長との短い方までで確認する

hirohisohirohiso

mapを使うか、arrayを使うか
キーがint値 N = 10000 くらいならarrayにしたほうがO(1)でアクセスできる。
(HashMapでもいいけど・・・・

hirohisohirohiso

時間がかかってしまった。
https://atcoder.jp/contests/abc311/tasks/abc311_c

UF → 有向辺だと使えない
SCC → 連結成分を抽出できるけど、今回だと重め

問題の制約からどの点から出発しても閉路に入るということに気づけなかった。
また、閉路発見時の処理がうまくできていない。

N回移動しておけば必ず閉路内にいるというのは盲点だった。

hirohisohirohiso

ハマった。。。。。

オートボクシングに頼ってInteger型のまま比較をすると以下のコードはたまに数値として同じでも
falseを返却する。
sp.b == tp.b

Integer型はオブジェクトの値の一致保証は==ではされない。値が小さいとキャッシングしたオブジェクトを返却するため== でも一致する場合がある。

明示的にintにキャストするか、
equalメソッドを使うこと
(int)sp.b == (int)tp.b

https://www.jmuto.info/2022/05/java-integer-comparison.html

hirohisohirohiso

https://atcoder.jp/contests/abc103/tasks/abc103_c

解とは真逆のf(0)を満たすm ( m> 1)がもとまれば、m -1がもっとも最大となる。
m %anが0なら m -1 % anは an -1となるため。
f(0)を満たすmは最小公倍数。

→解の逆や近傍を考える。
逆/近傍を求めることが容易く、そこから解へも出せると良い

hirohisohirohiso

https://atcoder.jp/contests/abc174/tasks/abc174_d

RRRRRWWWWWという感じに寄せれば良い。

最初の思ったこと

累積和?→考察まとまらず
DP? →うまく状態を作れなさそう
答えで二部探索?→回数を固定にしてもYes/No判定が難しいそう
貪欲?→操作が2種類あり貪欲出来なさそう
RRRRR
WWWWW
みたいに被っている範囲を見つけてなんかやる?

二つの操作がある
→どちらかをもう一つの操作に置き換えるのが定石
色を変える操作は、交換の操作に置き換えられそう。

右と左から見ていき、W .... R となった箇所を交換していけば、目的の状態になりそう。
それが最小回数かは不明。

hirohisohirohiso

アライグマ「D問題は要するに「RR...WW...」になるようにすればいいのだ。どう考えても「左の方にあるWと右の方にあるRを入れ替えていく」のが一番いいのだ!」

なるほど?

hirohisohirohiso

累積和?→考察まとまらず

こちでもいけそう(区間和)。公式解説と同様、WWWW/RRRRの敷居となる場所を固定にすれば、
WとRの数から定数時間で解が求められる。

hirohisohirohiso

ループ中の配列の確保に注意。 配列確保は定数倍時間で終わらない

hirohisohirohiso

TreeMapのremove(key,val); valだったらkeyを削除
firstKey,lastKey,mergeあたりは頭に入れておく。