競技プログラムの覚書
点の回転と移動
var _x1 = (Math.cos(dth) * _x) - (Math.sin(dth) * _y) + cx;
var _y1 = (Math.sin(dth) * _x) + (Math.cos(dth) * _y) + cy;
intelli J はcommand + n
でコンストラクタ作成できる
for文を使い、空白区切りで出力する時
i < n ? " " : ""
で最後に空白出さないようにできる
部分和はdp
Stringの配列をcharのストリームにしたい
String[] strArray = {"Hello", "World"};
Stream<Character> stream = Arrays.stream(strArray)
.flatMap(str -> str.chars().mapToObj(c -> (char) c));
String.chars()
でcharのIntStreamが出てくる
確率問題でも母数が10^5とかの場合は、数えあげでシンプルに解ける可能性がある
プリミティブ配列をColletionにaddAllする
var set = new HashSet<>();
set.addAll(Arrays.stream(An).boxed().collect(Collectors.toList()));
boxedでボクシング変換できる
集合Sの部分集合が与えられた時に、含まれていない物を出す。
Setに集合Sの要素を入力し、部分集合の要素を削除していけば、
Setに残ったものが答えになる
10^6台のint * int はオーバーフローするので注意
プリミティブ配列を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
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);
LinkedListはgetがO(n)になる.
ArrayListはO(n)
利用は注意
割り切れるだけの判定なら余りのみを管理すればdpっぽい感じに計算できる
よく忘れるComparatorの定義の例。
ジェネリクスはメソッドの前.
staticメソッドの場合は、メソッドの前に「クラス名.<型>」を書く。
var comparator = Comparator.<Pair>comparingInt(p -> p.b).reversed().thenComparingInt(p -> p.a);
mapのmergeはたまに使うかも
map.merge(key, msg, String::concat);
map.merge(key,valuew, Long::sum);
文字列の判定は普通に正規表現が一番早い・・・
String s = in.next();
if (s.matches("[A-Z][1-9][0-9]{5}[A-Z]")) {
out.println("Yes");
} else {
out.println("No");
}
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");
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;
}
}
true/falseのdp[i]は,iをsetとかに格納すれば、より高速に解けるんでは?
dp[i] = true みたいなのを、 set.add[i]にする
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);
Double.MIN_VALUEの意味を勘違いしていた。正の値の最小表現なのか・・・
マイナスの無限大は
Double.NEGATIVE_INFINITYで表す
コンテスト後に拾ってきた気になるワード
順列列挙、組み合わせ列挙
マージテク
答えに影響のない値を入力値に加えることで、
if文が存在しないようなロジックが作れればシンプルに書ける。
*配列を2つづつ処理していく場合などに、奇数個に0を加えて偶数にするなど(奇数個の条件削除)
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));
配列にidxを格納する
Arrays.setAll(c,i->i);
区間に辺を張る
ソートしまくる解法
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]);
}
二部グラフ判定、二部マッチング
最小費用流
Zindex,SA,ロリは
Grandy数
点素パス、辺素パス
領域の重なっている判定みたいなもの
複数領域あっても、LのmaxとRのminの値で、全て重なっている部分がわかる
集合Aが集合Bの部分集合であるかは
B.containsAll(A)
使えば一発
複数回のテストケースに回答できるパターン。 O(1)で回答。
文意を誤って捉えた。与えられた文字列から逆引きのテーブル作らないと
辿れる道が高々一本だけだった・・・
999,999 ~ 1,000の数
999,999,999 ~ 1,000,000の数
とカンマの数軸で数え上げてたけど、
1個前のカンマがある数字はいくつあるか? の方が圧倒的に楽だった。
並び替えて両者が一致するか?は、ソートして前から順に見ていくのが簡単。
列に対する操作は、回転して行と列を入れ替えるのが良い。
なんとなく'-'でスプリットしたら答え出そうと思ったけど、その通りだった
多分解法1で解いた。 並べ替えた後に小さい列と大きい列の二つに分けて、先頭から順に
配置していく。 配置していく際に、Bn[i] < Bn[i+1] とBn[i+2] < Bn[i+1]が成り立つことを常に確認する。
解法2は難しそう
過半数以上取得した値を判定するアルゴリズム
誰かを帰らせるために最適な座らせ方を自由に指定してもそれが実現できないということと等価
尺取り法
左端をforで増やしつつ、右端をwhileで条件満たさなくなるまで増やすやり方
var end = 0;
var ans = 0;
for (int start = 0; start < N; start++) {
while(end < N && An[end] - An[start] < M){
end++;
}
ans = Math.max(ans,end -start);
}
一重しゃくとり
自分はdp
速い人はBit全探索(2^18)
計算量見積もれば全探索(シミュレーション?)しても間に合う。
操作がたかだか何回行うかを、最大値 - 最小値の変化から見積もる
上位コードは10000回くらい実行しても解が得られなかったら-1出力とする思い切ったコード
bit全探索でビットが立っている個数がNの時に表示するってやったけど、解放は違う方法だった。
トップ解法は再帰関数が多そう (N番目の項はN-1番目の数 より大きくM以下)
3点が直線である条件
3点の面積
ツリーをイメージして、セグ木のように子から親を辿るかと思った。
平衡じゃないのでlogNが保証されない。
単純に2N +1の配列を用意して、子供ができるたびに世代+1して記録していけば良いだけ
この時かたで解いた
ダイクストラ?ぽさ味も感じてたら、実際にダイクストラで解けるみたい
まぁ実際には各点に入る辺が2辺しかないので、ダイクストラよりは最初の方が効率いい
存在判定はペアをHashSetに格納するのが一番楽
var set = new HashSet<Pair>();
set.add(new Pair(a,b));
set.remove(new Pair(a,b));
set.contains(new Pair(a,b))
xx,xy,yx,yyを
x->x, x->y,y->x,y->yと表現するとオイラー路が存在するかの判定問題になる
これと同じような考え方
BEST theorem
操作を言い換える
ソートした後に、前から順に貪欲に見ていくであっていた。(ただしいかは説明できない)
A1を選択すると、Bj <= A1となる全ての要素は選択されることがない。
A1 < Bj となるうちで一番小さい要素をB1とする。
同様の考え方で Ck < B1となる全ての要素は選択されることがない。
そうやって選ばれたA1,B1,C1は解の一つ
ある最適解を考えた時に、
解を最小値/最大値で入れ替えても条件を満たすような時、
貪欲法が適用できる・・・?
地味に実装がめんどい
八方向のテクニック
差分を配列で持っておく
難しかったが、解説と同様な考察でいけた
これを3分で通す人ってなんなんだ・・・(自分は30分かかった
シミュレーション問題。 実装力。
35分くらいかかってしまった・・・・
じゃんけんの勝ち負け判定とバグとり
試した解法
Anからsetを作り並び替え、An[i]ごとにその数値以上の数の種類を計算、カウント
最後にKごとにカウント値を出力
(シミュレーション的)
解説の解法
An[i] = An[j] とすると(i != j)
その数値以上の数の種類は固定。なので同時に求めることができる。
よくわかってないけど、nextpermutationの値から計算ができる?????
みんな(N - Pn[i])を表示しているっぽい
bit全探索スニペット化したい
var bit = 0;
while(bit < (1 << M)){
for (int i = 0; i < M; i++) {
if(((bit >> i) & 1) == 1){
}
}
あとで読む
グラフみたいなイメージを持った。
i番目まで見た時、i-1番目までの結果によらずi+1番目の到達判定ができるので、
dpで十分。
小数にビビって実装していたらバグり散らかした
ビビらないコード
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);
}
転倒数のコードサンプル
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);
文章読むのと、No出力の判定に時間がかかった
絶対値とかつけたままにして大小比較。バグ埋め込んで探すのに時間
先頭が1なら2文字目、そうじゃないなら1文字目で最初は考えたが、
先頭から1がN文字連続し、K < Nだった場合は1となる。
そのためK文字目まで1が連続していないかを確認し、初めて1じゃない数が来た場合はその数が答えになる。
ただしKがとても大きいので、 KとSの文字列長との短い方までで確認する
mapを使うか、arrayを使うか
キーがint値 N = 10000 くらいならarrayにしたほうがO(1)でアクセスできる。
(HashMapでもいいけど・・・・
時間がかかってしまった。
UF → 有向辺だと使えない
SCC → 連結成分を抽出できるけど、今回だと重め
問題の制約からどの点から出発しても閉路に入るということに気づけなかった。
また、閉路発見時の処理がうまくできていない。
N回移動しておけば必ず閉路内にいるというのは盲点だった。
ハマった。。。。。
オートボクシングに頼ってInteger型のまま比較をすると以下のコードはたまに数値として同じでも
falseを返却する。
sp.b == tp.b
Integer型はオブジェクトの値の一致保証は==ではされない。値が小さいとキャッシングしたオブジェクトを返却するため== でも一致する場合がある。
明示的にintにキャストするか、
equalメソッドを使うこと
(int)sp.b == (int)tp.b
解とは真逆のf(0)を満たすm ( m> 1)がもとまれば、m -1がもっとも最大となる。
m %anが0なら m -1 % anは an -1となるため。
f(0)を満たすmは最小公倍数。
→解の逆や近傍を考える。
逆/近傍を求めることが容易く、そこから解へも出せると良い
条件式を作って整理すれば単純化できる
RRRRRWWWWWという感じに寄せれば良い。
最初の思ったこと
累積和?→考察まとまらず
DP? →うまく状態を作れなさそう
答えで二部探索?→回数を固定にしてもYes/No判定が難しいそう
貪欲?→操作が2種類あり貪欲出来なさそう
RRRRR
WWWWW
みたいに被っている範囲を見つけてなんかやる?
二つの操作がある
→どちらかをもう一つの操作に置き換えるのが定石
色を変える操作は、交換の操作に置き換えられそう。
右と左から見ていき、W .... R となった箇所を交換していけば、目的の状態になりそう。
それが最小回数かは不明。
アライグマ「D問題は要するに「RR...WW...」になるようにすればいいのだ。どう考えても「左の方にあるWと右の方にあるRを入れ替えていく」のが一番いいのだ!」
なるほど?
累積和?→考察まとまらず
こちでもいけそう(区間和)。公式解説と同様、WWWW/RRRRの敷居となる場所を固定にすれば、
WとRの数から定数時間で解が求められる。
ループ中の配列の確保に注意。 配列確保は定数倍時間で終わらない
外積だった
TreeMapのremove(key,val); valだったらkeyを削除
firstKey,lastKey,mergeあたりは頭に入れておく。