Hatena Blog Archive from 2011-02-16 to 2024-08-28
はてなブログの公開記事とコメントのアーカイブです。日時は日本時間です。画像・埋め込みは外部サービスを参照しています。
『Binary Hacks Rebooted―低レイヤの世界を探検するテクニック89選』に込めた思い
· 元記事
はじめに
2024年8月28日に『Binary Hacks Rebooted―低レイヤの世界を探検するテクニック89選』という本の販売が開始されました。 著者の一人として、この本の紹介をします。 お持ちでない方は、Amazon や、楽天、お近くの書店などで購入が可能です。 購入していただけると大変嬉しいです。 どうぞよろしくおねがいします。
どのような本か
『Binary Hacks Rebooted』は2006年に発売された『Binary Hacks』の再構築版です。 再構築版と聞くと『Binary Hacks』とどれくらい異なるのが気になると思いますが、ほぼすべてが異なります。 詳細な目次はこちら で確認できますが、『Binary Hacks』からそのまま引き継いだ Hack は一つもなく、全てが新しい Hack です。
『Binary Hacks Rebooted』は低レイヤプログラミングの技術を幅広く、かつ深く解説した書籍です。 「低レイヤプログラミング」という単語は広い意味を持ちますが、この書籍ではELF フォーマットの解析からファームウェアの作成、浮動小数点例外まで幅広い内容を扱っています。 そして、各トピックについて日本語はもちろん、英語でも類似する解説が少ないと思われる内容を紹介しています。 カーネル/VM探検隊 に参加したことがある方は、「読むカーネル/VM探検隊」のような感覚で読んでいただけるかと思います。
この本に込めた思い
私達はこの本を2つのことを目標に書いていました。
1つ目は「低レイヤプログラミングを面白いと感じてもらう」ことです。 細かいことはわからなくても構いません。 「あーこんなことできるんだ、おもしろ」と思ってもらえれば大変嬉しいです。 あなたが好きなように「探検」してみてください。
低レイヤプログラミングは一見とっつきにくい分野ですが、一度どのように動いているのかを理解すると、様々な面白いことができるようになります。 例えば、自分だけのファームウェアを作ってChromebook を動かすこともできますし、DWARF を使って四則演算を行うこともできます。 いつかどこかで私と会った時に、Hack の内容で盛り上がれることを楽しみにしています。
2つ目は「低レイヤプログラミングで困っている方の助けになる」ことです。 低レイヤプログラミングを実際に行ってみると、不可解なバグに多数直面します。 そのうちの多くは把握していない仕様、挙動によるものが多く、このようなバグを解決するためには幅広い分野の知識が必要です。 ELF ハックの多くは私が過去に実際に知りたかった知識をまとめたものが多く、同様のバグで困っている方の助けになればと思っています。
Thread Local Storage (TLS) の解説はその一例です。 TLS は非常に多くのプログラムで人知らず使われている機能ですが、低レイヤプログラミングを実際に行っているとその仕組みを理解するように迫られることがあります。 ところが、TLS についての詳細な解説は日本語でも英語でもあまり見かけません。 「#10 TLSのしくみを理解する 」は誰よりも3年前の過去の自分に当てて書いた Hack です。 この内容を3年前に知っていたら何ヶ月もデバッグに時間を費やすことはなかったでしょう。
私と同様に意味不明な挙動に悩まされている方が、世の中にはきっとたくさんいるはずです。
segmentation fault (core dumped) を見飽きたときに、この本を開いてみてください。
どんなふうに読んでほしいか
この本は最初のページから順番に読んでいく必要はありません。 いくつかの続きものの Hack を除いて各 Hack は独立して読めるようになっています。 目次を見て面白そうなところを読んでみたり、サイコロを振って出た目の Hack を読んでみたりしてください。 どんな方でもパラパラとめくっているうちに意外な気づきがあるはずです。
サンプルコードを実際に動かして見てから文書をしっかり読んでみるのも良いかもしれません。 サンプルコードはhttps://github.com/oreilly-japan/binary-hacks-rebooted/にあります。
おわりに
この本を書くために膨大な時間を費やし、各種の仕様書、Linux や glibc のソースコードを何度も読み返しました。 自信を持って面白い本だと言えます。 この本を読んで、一人でも「低レイヤプログラミングを面白い」と感じてもらえる方がいれば幸いです。
最後に、もし、あなたが2042年ごろにこの文書を読んでおり、『Binary Hacks』の再再構築版が出したいと思っているなら、ぜひ私に連絡してください。 喜んで協力したいです。
2024年8月28日
河田 旺
gcc ビルトイン関数の呼び出し
· 元記事
ダイナミックリンカのソースコード(elf/rtld.c)を見ていると興味深いコメントがあった。
/* Partly clean the `bootstrap_map' structure up. Don't use
`memset' since it might not be built in or inlined and we cannot
make function calls at this point. Use '__builtin_memset' if we
know it is available. We do not have to clear the memory if we
do not have to use the temporary bootstrap_map. Global variables
are initialized to zero by default. */
「memsetはビルドインでないもしくはインライン化されていない可能性があるため、ここでは呼び出してはいけない」。このコメントはダイナミックリンカの起動直後にあるため、再配置が終わっていない関数を呼び出すとアドレスが埋まっていないGOTを参照してしまい、Segmentation Faultが発生することを危惧したものだと考えられる。
ということはgccのビルトイン関数は必ずインライン化されなければならない。実際gccのビルトイン関数の説明には以下のような記述がある。
With the exception of built-ins that have library equivalents such as the standard C library functions discussed below, or that expand to library calls, GCC built-in functions are always expanded inline and thus do not have corresponding entry points and their address cannot be obtained. Attempting to use them in an expression other than a function call results in a compile-time error.
ビルトイン関数を使う小さなプログラムでも実験的に確かめることができる。
> cat main.c
#include <stdio.h>
int main() {
int c = __builtin_clz(0x10101);
printf("c = %d\n", c);
return 0;
}
> gcc main.c -static -o main
> objdump -d main | grep "<main>" -A 16
0000000000401ce5 <main>:
401ce5: f3 0f 1e fa endbr64
401ce9: 55 push %rbp
401cea: 48 89 e5 mov %rsp,%rbp
401ced: 48 83 ec 10 sub $0x10,%rsp
401cf1: c7 45 fc 0f 00 00 00 movl $0xf,-0x4(%rbp)
401cf8: 8b 45 fc mov -0x4(%rbp),%eax
401cfb: 89 c6 mov %eax,%esi
401cfd: 48 8d 3d 00 33 09 00 lea 0x93300(%rip),%rdi # 495004 <_IO_stdin_used+0x4>
401d04: b8 00 00 00 00 mov $0x0,%eax
401d09: e8 92 ee 00 00 callq 410ba0 <_IO_printf>
401d0e: b8 00 00 00 00 mov $0x0,%eax
401d13: c9 leaveq
401d14: c3 retq
401d15: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
401d1c: 00 00 00
401d1f: 90 nop
__builtin_clzはインライン化されているようだ。
LD_AUDITとGlobal Offset Table
· 元記事
GNU製のLinux向け動的リンカld-linux.so には環境変数 LD_AUDIT 経由で使える監視APIがあります。このAPIを使えば、ld-linux.soの様々な挙動にフックして、その挙動を監視・干渉することができます。また、興味深いことにLD_AUDITを有効にするとGOT(Global Offset Table)周りの挙動も変化します。
PLT(Procedure Linkage Table)やGOTについて知らない場合はこちらの記事が分かりやすいです。この記事のglibcはすべてコミット 137ed5ac44を参照しています。
動作例
LD_AUDITを使ってltrace(1)まがいのものを作ってみます。以下の3つのファイルを作ります。なお、このサンプルはLinux glibcで、LD_AUDIT機能による関数トレースを参考にしました。
audit.c
#define _GNU_SOURCE #include <link.h> #include <stdio.h> unsigned int la_version(unsigned int version) { return LAV_CURRENT; } unsigned int la_objopen(struct link_map* map, Lmid_t lmid, uintptr_t* cookie) { printf("[LD_AUDIT] %s loaded\n", map->l_name); return LA_FLG_BINDTO | LA_FLG_BINDFROM; } ElfW(Addr) la_x86_64_gnu_pltenter(ElfW(Sym) * sym, unsigned int ndx, uintptr_t* refcook, uintptr_t* defcook, La_x86_64_regs* regs, unsigned int* flags, const char* symname, long* framesizep) { printf("[LD_AUDIT] %s entering\n", symname); return sym->st_value; }
hello.c
#include <stdio.h> int main() { puts("Hello World!"); puts("Hello World!"); puts("Hello World!"); return 0; }
run.sh
#! /bin/bash -eux gcc -o libaudit.so -shared -fpic -fPIC -Wl,-soname,libaudit.so audit.c gcc -o hello hello.c
run.sh を実行すると以下のような結果が得られて、ltraceっぽいことができているのがわかります。
> ./run.sh + gcc -o libaudit.so -shared -fpic -fPIC -Wl,-soname,libaudit.so audit.c + gcc -o hello hello.c + LD_AUDIT=libaudit.so + ./hello [LD_AUDIT] loaded [LD_AUDIT] /lib64/ld-linux-x86-64.so.2 loaded [LD_AUDIT] /lib/x86_64-linux-gnu/libc.so.6 loaded [LD_AUDIT] _dl_find_dso_for_object entering [LD_AUDIT] __tunable_get_val entering [LD_AUDIT] __tunable_get_val entering [LD_AUDIT] __tunable_get_val entering [LD_AUDIT] __tunable_get_val entering [LD_AUDIT] __tunable_get_val entering [LD_AUDIT] __tunable_get_val entering [LD_AUDIT] __tunable_get_val entering [LD_AUDIT] __tunable_get_val entering [LD_AUDIT] __tunable_get_val entering [LD_AUDIT] __tunable_get_val entering [LD_AUDIT] __tunable_get_val entering [LD_AUDIT] __tunable_get_val entering [LD_AUDIT] puts entering Hello World! [LD_AUDIT] puts entering Hello World! [LD_AUDIT] puts entering Hello World! + LD_BIND_NOW=1 + LD_AUDIT=libaudit.so
解説
la_versionはLD_AUDITを使うために必ず必要な関数、la_objopenは新しいshared objectがロードされたときに呼び出される関数であり、今回は深入りしません。
la_x86_64_gnu_pltenterは PLT(Procedure Linkage Table)がGOT(Global Offset Table)を埋めるときに呼び出される関数であり、具体的にはここで呼び出されます。
この実行結果で興味深いのは [LD_AUDIT] puts entering が複数回出力されていることです。一度PLTのエントリがGOTに正しいアドレスを埋めた後は、putsの呼び出しはlibcのアドレスを埋める部分を通らず、libc中のputs本体に直接jmpしla_x86_64_gnu_pltenterを呼び出すとことはできないはずです。
ここから考えるとLD_AUDITが設定されているときはGOTにアドレスを埋める処理が行われない、と推測できます。glibcの実装を確認してみましょう。
LD_AUDITによって設定されたla_x86_64_gnu_pltenterはdl-runtime.cのL435-L439で呼び出されます。- これは dl-runtime.cの_dl_profile_fixupの中から呼ばれています。
- また、
_dl_profile_fixupにはGOTの値を更新する処理がありません。 - 一方、dl-runtime.cには _dl_fixupもあり、
LD_AUDITの有無で_dl_profile_fixupと_dl_fixupを切り替えているのだと推察できます。 _dl_fixupは dl-trampoline.hの_dl_runtime_resolveから、_dl_profile_fixupは dl-trampoline.hの_dl_runtime_profile からそれぞれ呼び出されています。- これら2つの関数のどちらかが dl-machine.hで
got[2]に設定されます。 - この分岐を決定するのは
elf_machine_runtime_setupのprofile引数で dynamic-link.hのELF_DYNAMIC_RELOCATEが呼び出し元です。 - このマクロはdl-reloc.cで呼び出されています。
- dl-reloc.cの_dl_relocate_objectの最初の方で
consider_profilingがLD_AUDITの有無に従って設定されています。よって、LD_AUDITが設定されているときは_dL_profile_fixupが呼び出され、GOTが更新されないことがわかります。
また、dl-machine.hにはprofiling extensionが使用されている場合、GOTを書き換えない旨のコメントがあります。
/ The got[2] entry contains the address of a function which gets called to get the address of a so far unresolved function and jump to it. The profiling extension of the dynamic linker allows to intercept the calls to collect information. In this case we don't store the address in the GOT so that all future calls also end in this function. /
参考
GNU_IFUNCとは何か
· 元記事
GNU_IFUNC(GNU indirect function)とは同一の関数の複数の実装から、ロード時に最適な実装を選択する仕組みです1。通常、同一の関数の複数の実装が存在した場合、動的リンカ(ld-linux.so)が最初に見つけた実装が選択されますが、その選択するための基準を開発者が決定できるところに、この機能の意義があります。
GNU_IFUNCを使った簡単な例を以下に示します。resolve_foo で foo の実装が選択されていることがわかります。
> cat foo.c
#include <stdio.h>
extern void foo();
void foo_default() { printf("foo_default\n"); }
void foo_1() { printf("foo_1\n"); }
void foo_2() { printf("foo_2\n"); }
void foo() __attribute__((ifunc("resolve_foo")));
static void *resolve_foo(void) {
if (0)
return foo_1;
else if (42 == 41 + 1)
return foo_2;
else
return foo_default;
}
> cat main.c
void foo();
int main() {
foo();
return 0;
}
> gcc -shared -fpic -fPIC foo.c -o libfoo.so
> gcc -o main main.c libfoo.so
> ./main
foo_2
glibcでは memmove, memset, memcpy, strcmp, strstr などの関数でCPUの機能に応じて高度に最適化された実装をロード時に選択するために使われています2。例えば x86-64のmemcpy の場合はAVX512等のSIMD拡張が使えるかどうかを判定し、最適な実装を選択するコードが入っています。3
/* glibc/sysdeps/x86_64/multiarch/ifunc-impl-list.c */
IFUNC_IMPL (i, name, __memcpy_chk,
IFUNC_IMPL_ADD (array, i, __memcpy_chk,
CPU_FEATURE_USABLE (AVX512F),
__memcpy_chk_avx512_no_vzeroupper)
IFUNC_IMPL_ADD (array, i, __memcpy_chk,
CPU_FEATURE_USABLE (AVX512VL),
__memcpy_chk_avx512_unaligned)
IFUNC_IMPL_ADD (array, i, __memcpy_chk,
CPU_FEATURE_USABLE (AVX512VL),
__memcpy_chk_avx512_unaligned_erms)
IFUNC_IMPL_ADD (array, i, __memcpy_chk,
CPU_FEATURE_USABLE (AVX),
__memcpy_chk_avx_unaligned)
最後にGNU_IFUNCの実装選択関数を悪用してみます。glibcのGNU_IFUNCに関する再配置情報を処理する部分を見ると、ロード時に実装を選択するための関数が呼ばれていることがわかります4。
/* glibc/sysdeps/x86_64/dl-machine.h */ else if (__glibc_unlikely (r_type == R_X86_64_IRELATIVE)) { ElfW(Addr) value = map->l_addr + reloc->r_addend; if (__glibc_likely (!skip_ifunc)) value = ((ElfW(Addr) (*) (void)) value) (); *reloc_addr = value; }
つまり、__attribute__(constructor)5 と同様に main() の前に関数を呼び出す手段として使えそうです。やってみましょう。
> cat foo.c
#include <stdio.h>
extern void foo();
void foo_default() { printf("foo_default\n"); }
void foo_1() { printf("foo_1\n"); }
void foo_2() { printf("foo_2\n"); }
void foo() __attribute__((ifunc("resolve_foo")));
static void *resolve_foo(void) {
printf("resolve_foo\n");
if (0)
return foo_1;
else if (42 == 41 + 1)
return foo_2;
else
return foo_default;
}
> cat main.c
#include <stdio.h>
void foo();
int main() {
printf("beginning of main()\n");
foo();
return 0;
}
> gcc -shared -fpic -fPIC foo.c -o libfoo.so
> gcc -o main main.c libfoo.so
> LD_BIND_NOW=1
> ./main
resolve_foo
beginning of main()
foo_2
R_X86_64_IRELATIVE が main() 起動後に遅延して解決されるのを防ぐために LD_BIND_NOW=1 が必要でしたが、無事 main() の前に resolve_foo を呼び出せたことがわかります。
setjmp, longjmpで使われるjmp_bufには何がどのように保存されているのか
· 元記事
setjmp, longjmpはC言語で実行コンテキストを保存し、保存したコンテキストに復帰するために使われる関数(orマクロ)である。g++の例外実装の一つはこのsetjmp, longjmpを用いて実現されている。
この記事では 実行コンテキスト とは何を指すのか? また、その保存方法の実装について調べる。
jmp_buf
setjmpの実装はglibcの setjmp/setjmp.h にある。
typedef struct __jmp_buf_tag jmp_buf[1]; /* Store the calling environment in ENV, also saving the signal mask. Return 0. */ extern int setjmp (jmp_buf __env) __THROWNL;
このjmp_bufはsetjmp/bits/types/struct___jmp_buf_tag.h で定義されている。
/* Calling environment, plus possibly a saved signal mask. */ struct __jmp_buf_tag { /* NOTE: The machine-dependent definitions of `__sigsetjmp' assume that a `jmp_buf' begins with a `__jmp_buf' and that `__mask_was_saved' follows it. Do not move these members or add others before it. */ __jmp_buf __jmpbuf; /* Calling environment. */ int __mask_was_saved; /* Saved the signal mask? */ __sigset_t __saved_mask; /* Saved signal mask. */ };
x86-64の場合、__jmp_bufはsysdeps/x86/bits/setjmp.hで定義されている。__jmp_bufは8個のint型整数からなり、更に64bitかどうかで場合分けがなされていることがわかる。
# if __WORDSIZE == 64 typedef long int __jmp_buf[8]; # elif defined __x86_64__ __extension__ typedef long long int __jmp_buf[8]; # else typedef int __jmp_buf[6]; # endif
setjmp
setjmp自体の実装はsysdeps/x86_64/setjmp.Sにある。Linux x86-64では第一引数を%rdiで渡すので、第一引数として渡された構造体にレジスタの値を保存していることがわかる。保存されているのは、%rbx, %r12, %r13, %r14, %r15, %rsp, PCである。しかし、%rsp, %rbp, PCはPTR_MANGLEで処理されてから保存されている。PTR_MANGLEとは何だろうか? また、なぜ必要なのだろう?
ENTRY (__sigsetjmp)
/* Save registers. */
movq %rbx, (JB_RBX*8)(%rdi)
#ifdef PTR_MANGLE
# ifdef __ILP32__
/* Save the high bits of %rbp first, since PTR_MANGLE will
only handle the low bits but we cannot presume %rbp is
being used as a pointer and truncate it. Here we write all
of %rbp, but the low bits will be overwritten below. */
movq %rbp, (JB_RBP*8)(%rdi)
# endif
mov %RBP_LP, %RAX_LP
PTR_MANGLE (%RAX_LP)
mov %RAX_LP, (JB_RBP*8)(%rdi)
#else
movq %rbp, (JB_RBP*8)(%rdi)
#endif
movq %r12, (JB_R12*8)(%rdi)
movq %r13, (JB_R13*8)(%rdi)
movq %r14, (JB_R14*8)(%rdi)
movq %r15, (JB_R15*8)(%rdi)
lea 8(%rsp), %RDX_LP /* Save SP as it will be after we return. */
#ifdef PTR_MANGLE
PTR_MANGLE (%RDX_LP)
#endif
movq %rdx, (JB_RSP*8)(%rdi)
mov (%rsp), %RAX_LP /* Save PC we are returning to now. */
LIBC_PROBE (setjmp, 3, LP_SIZE@%RDI_LP, -4@%esi, LP_SIZE@%RAX_LP)
#ifdef PTR_MANGLE
PTR_MANGLE (%RAX_LP)
#endif
movq %rax, (JB_PC*8)(%rdi)
#ifdef SHADOW_STACK_POINTER_OFFSET
# if IS_IN (libc) && defined SHARED && defined FEATURE_1_OFFSET
/* Check if Shadow Stack is enabled. */
testl $X86_FEATURE_1_SHSTK, %fs:FEATURE_1_OFFSET
jz L(skip_ssp)
# else
xorl %eax, %eax
# endif
/* Get the current Shadow-Stack-Pointer and save it. */
rdsspq %rax
movq %rax, SHADOW_STACK_POINTER_OFFSET(%rdi)
# if IS_IN (libc) && defined SHARED && defined FEATURE_1_OFFSET
L(skip_ssp):
# endif
#endif
#if IS_IN (rtld)
/* In ld.so we never save the signal mask. */
xorl %eax, %eax
retq
#else
/* Make a tail call to __sigjmp_save; it takes the same args. */
jmp __sigjmp_save
#endif
END (__sigsetjmp)
PTR_MANGLEは sysdeps/unix/sysv/linux/x86_64/sysdep.h に定義がある。
# define PTR_MANGLE(reg) xor %fs:POINTER_GUARD, reg; \
rol $2*LP_SIZE+1, reg
# define PTR_DEMANGLE(reg) ror $2*LP_SIZE+1, reg; \
xor %fs:POINTER_GUARD, reg
Linux x86-64では%fsレジスタがThread Local Storageのベースポインタとして使われることを思い出すと、このコードは
- スレッド固有の値(
%fs:POINTER_GUARD)とのxorを取り $2*LP_SIZE+1だけレジスタのビットを回転させる
ことがわかる。PTR_MAGNLEの詳細は Pointer Encryptionにある。
なぜsetjmp, longjmpに際してポインタを暗号化するのだろうか? 最近のglibcではatexit関数やjmp_bufを狙った攻撃は効かない (PTR_MANGLE)やPointer Subterfugeによれば、書き込み可能な領域に生のEIPを保存すると攻撃対象になるからだそうだ。
結論
- setjmpは
%rbx,%r12,%r13,%r14,%r15,%rsp, PCをjmp_bufに保存する - このとき、生のEIPを保存するとそのデータ自体が攻撃目標になるため、PTR_MANGLEで暗号化する
検索エンジンを自作する夢
· 元記事
そろそろ学生も終わるので、1年前の失敗と反省を書き留めておこうと思います。私達(@a_kawashiroと@gky360)は2018年度の未踏プロジェクトで分野限定型検索エンジンを複数組み合わせた分散型検索エンジンとして採用され、9ヶ月間を検索エンジンの作成に費やしました。この記事ではプロジェクトの紹介と、作成した検索エンジンが満足な性能を出せなかった原因について述べます。
膨大なインターネットの情報の海の中で目的の情報に辿り着くためには、検索エンジンが必要不可欠です。しかし世の中のメジャーな検索エンジンは大企業が提供してくれているものばかりで、ユーザが自分で管理・設定できるものはほとんどありません。それなら一つ作ってみるか、ということで始めたのがこのプロジェクトでした。
検索エンジンは大まかに言って3つの機能で構成されます。
- クロール: インターネット上からウェブページをダウンロードして情報を蓄積する
- 検索: 蓄積した情報のなかからユーザのクエリにマッチするものを探し出す
- 表示: マッチするものを適切に並び替えて表示する
どれも最低限の実装をするならそれほど難しくないので、検索エンジンを作るのはそれほど難しくないように思われます。
しかし、現在のインターネットはあまりに巨大です1。千里眼やMondouが作られた時代はせいぜい300万サイトしかありませんでしたが、今は16億サイトを超えていて素直な方法で検索エンジンを作ってもまともな結果が帰ってこないのは明らかです。
Googleのような巨大なデータセンターを使えればよいのですが、お金に余裕がなかったので、インターネット上の個人のサーバをうまく協力させて一つの検索エンジンを構成することにしました。このような検索エンジンは分散検索エンジンと呼ばれ、yacyなどがよく知られています。yacyは完全なpeer to peer型の分散検索エンジンですが、私達は
という設計を採用することにしました。この設計のメリットは、
- 各分野ごとの検索エンジンはその分野に特化することで、大きなリソースを使わずに精度の高い検索サービスを提供でき、
- 更にそれらを複数つなぎ合わせることでより広範囲の検索サービスを段階的に構築できる
の2点です。完全なpeer to peer型の分散検索エンジンに比べて、ある程度の階層構造があることで検索精度の改善が期待できます。
この設計を元に実装したのが、kearchです。kearchには2つの検索エンジンの実装が含まれます。一つは専門検索エンジン(各分野ごとの検索エンジン)、もう一つはメタ検索エンジン(専門検索エンジンをつなぎ合わせるための検索エンジン)です。
ユーザからのクエリはメタ検索エンジンによって適切な専門検索エンジンに割り振られ、ユーザはその専門検索エンジンの結果をメタ検索エンジンを通して受け取ります。例えばこの検索エンジンの構成で、"pip3 install flask"と検索するとPythonの専門検索エンジンサーバにクエリが飛んでPythonに関する検索結果が返ってきます。

2019年1月から3月にかけて、私達はさくらクラウド上で上の図の検索エンジンを実際に運用してみました。確かに専門検索エンジンもメタ検索エンジンも動くのですが実用には程遠い検索性能でした。その理由は以下の2つです。
まず、1つ目の「専門検索エンジンの数が足りない」ですが、これはつまり、人間が日常的に検索する分野の範囲が広すぎるということです。ブラウザの履歴を確認してもらうとわかるのですが、1人の人間が1ヶ月で検索する分野は非常に広大です。明らかに4台では足りませんでした。この問題に対する解決策は未だに思いついていません。
2つ目の「専門検索エンジンのクロール性能が低い」は具体的にはwebページの分類性能が低いということです。現在のkearchでは該当分野の単語リストを使ってwebページを分類するのですが、単語リストだけでは十分な性能が出ません。該当分野と全く関係ないwebページがクロールされていることもよくありました。
2つ目の問題にはいくつかの解決策が考えられます。例えば、該当分野の論文を読み込ませて単語リストを拡充したり、信頼できるwebページからのホップ数を考慮することで分類精度を改善できるでしょう。またFessなどのエンタープライズサーチ向けの製品を専門検索エンジンとして使えるように改造するのも解決策の一つだと思います。
最後に、専門検索エンジンについては様々な問題を感じる一方、メタ検索エンジンについてはさほど問題を感じませんでした。「小さな検索エンジンをつなぎ合わせてより大きな検索エンジンを作る」という基本設計は間違っていないと今でも思っています。
formalized-egison -- Egisonの型安全性の証明に向けて
· 元記事
概要
Egisonのパターンマッチの動作を定理証明支援系Coq上で定義し、「Egisonの処理系が項Mを値vに評価する」という命題をCoq上で表現できるようにしました。
動機
Egisonの中核的な機能はユーザが拡張可能なパターンマッチ機構です。例えば以下に示すように、ペアに対してその順序を無視してパターンマッチすることが可能です。
(define $unordered-pair
(matcher
{[<pair $ $> [something something]
{[[$x $y] {[x y] [y x]}]}]
[$ something
{[$tgt {tgt}]}]}))
(match-all [1 2] unordered-pair {[<pair $a $b> [a b]]})
; ===> {[1 2] [2 1]}
しかし、その拡張性のためにこのパターンマッチの動作は大変複雑です。Egi, Nishiwaki (2018)はEgisonのパターンマッチの動作をbig-step styleの操作的意味論として次のように定義しています。

型健全性などのEgisonの性質を証明する際はこの操作的意味論を使うことになりますが、規則の種類、数が多いので証明を手で書くと間違えてしまいそうです。そこで今回の記事ではEgi, Nishiwaki (2018)の操作的意味論をCoqに書き直しました。
formalized-egison
formalized-egisonはEgi, Nishiwaki (2018)によるEgisonの操作的意味論をCoqに書き直したものです。意味論を定義している部分はEgison.vの以下の部分です。
Inductive eval : (env * tm * tm)-> Prop :=
| evarin : forall i Gamma t, (Gamma i) = Some t -> eval (Gamma, (tvar i), t)
| evarout : forall i Gamma, (Gamma i) = None -> eval (Gamma, (tvar i), (tvar i))
| eint : forall i e, eval (e, (tint i), (tint i))
| etpl : forall Gamma t1 t2 v1 v2, eval (Gamma, t1, v1) -> eval (Gamma, t2, v2) -> eval (Gamma, ttpl t1 t2, ttpl v1 v2)
| ecll : forall e ts vs, same_length_list ts vs ->
Forall eval (map (fun tpl => let '(t,v) := tpl in (e,t,v)) (zip ts vs)) -> eval (e, (tcll ts), (tcll vs))
| epair : forall e t1 t2 v1 v2, eval (e, t1, v1) -> eval (e, t2, v2) -> eval (e, (tpair t1 t2), (tpair v1 v2))
| esm : forall Gamma, eval (Gamma, tsm, tsm)
(* | emtc : forall Gamma (ts: (list (pptn * tm * (list (dptn * tm))))), eval Gamma ((tmtc ts), (tmtc vs)) *)
| etplmtc : forall Gamma t1 t2 v1 v2, eval (Gamma, t1, v1) -> eval (Gamma, t2, v2) -> eval (Gamma, ttplmtc t1 t2, ttplmtc v1 v2)
| etmal : forall Gamma M N p L v_v v m_m m_e Delta_v, same_length_list Delta_v v_v -> eval (Gamma, M,v) -> evalmtc Gamma N [(m_m, m_e)] -> evalms3 [[([(p,m_m,m_e,v)], Gamma, empty)]] Delta_v ->
Forall eval (map (fun t => let '(d,v) := t in (Gamma @@ d, L, v)) (zip Delta_v v_v)) ->
eval (Gamma, (tmal M N (p, L)), tcll v_v)
with evalmtc : env -> tm -> list (tm * env) -> Prop :=
| emtcsm : forall Gamma, evalmtc Gamma tsm [(tsm, Gamma)]
| emtcmtc : forall Gamma l, evalmtc Gamma (tmtc l) [((tmtc l), Gamma)]
| emtctpl : forall Gamma m1 m2 n1 n2, eval (Gamma, (ttplmtc m1 m2), (ttplmtc n1 n2)) -> evalmtc Gamma (ttplmtc m1 m2) [(n1, Gamma); (n2, Gamma)]
with evaldp : dptn -> tm -> option env -> Prop :=
| edpvar : forall z v, value v -> evaldp (dpvar z) v (Some (z |-> v))
| edppair : forall p1 p2 v1 v2 g1 g2,
value v1 -> value v2 -> evaldp p1 v1 (Some g1) -> evaldp p2 v2 (Some g2) ->
evaldp (dppair p1 p2) (tpair v1 v2) (Some (g1 @@ g2))
| edpfail : forall t p1 p2, not (is_tpair t) -> evaldp (dppair p1 p2) t None
with evalpp : pptn -> env -> ptn -> option ((list ptn) * env) -> Prop :=
| eppdol : forall g p, evalpp ppdol g p (Some ([p], empty))
| eppvar : forall i g m v, eval (g, m, v) -> evalpp (ppvar i) g (pval m) (Some ([], (i |-> v)))
| epppair : forall pp1 pp2 p1 p2 g pv1 pv2 g1 g2,
evalpp pp1 g p1 (Some (pv1,g1)) -> evalpp pp2 g p2 (Some (pv2,g2)) ->
evalpp (pppair pp1 pp2) g (ppair p1 p2) (Some ((pv1 ++ pv2), (g1 @@ g2)))
| eppvarfail : forall y g p, not (is_pval p) -> evalpp (ppvar y) g p None
| epppairfail : forall pp1 pp2 p g, not (is_ppair p) -> evalpp (pppair pp1 pp2) g p None
with evalms1 : ((list ms) * option env * option (list ms)) -> Prop :=
| ems1nil : evalms1 ([], None, None)
| ems1anil : forall sv g d, evalms1 ((([],g,d)::sv), (Some d), (Some sv))
| ems1 : forall p m mg v av g d sv avv d1,
evalma (g @@ d) (p,m,mg,v) avv d1 ->
evalms1 ((((p,m,mg,v)::av, g, d)::sv), None, (Some ((map (fun ai => (ai ++ av, g, d @@ d1)) avv) ++ sv)))
with evalms2 : (list (list ms)) -> (list env) -> (list (list ms)) -> Prop :=
| ems2 : forall svv gvv svv1 gvv1 svv2,
same_length_list3 svv gvv svv1 ->
Forall evalms1 (zip3 svv gvv svv1) ->
(filtersome gvv) = gvv1 ->
(filtersome svv1) = svv2 ->
evalms2 svv gvv1 svv2
with evalms3 : (list (list ms)) -> (list env) -> Prop :=
| ems3nil : evalms3 [[]] []
| ems3 : forall svv gv svv1 dv gdv, evalms2 svv gv svv1 -> evalms3 svv1 dv -> gdv = gv ++ dv ->
evalms3 svv gdv
with evalma : env -> ma -> list (list ma) -> env -> Prop :=
| emasome : forall x g v d, evalma g (pvar x, tsm, d, v) [[]] (x |-> v)
| emappfail : forall p g pp m sv pv d v avv g1,
evalpp pp g p None -> evalma g (p,(tmtc pv),d,v) avv g1 ->
evalma g (p,tmtc ((pp,m,sv)::pv),d,v) avv g1
| emadpfail : forall p g pp m dp n sv pv d v pv1 d1 avv g1,
evalpp pp g p (Some (pv1, d1)) -> evaldp dp v None ->
evalma g (p, tmtc ((pp,m,sv)::pv),d,v) avv g1 ->
evalma g (p, tmtc ((pp,m,(dp,n)::sv)::pv),d,v) avv g1
| ema : forall p Gamma pp M dp N sigma_v Delta v phi1_v p1_v Delta1 Delta2 v1_vv m1_v,
evalpp pp Gamma p (Some (p1_v, Delta1)) ->
evaldp dp v (Some Delta2) ->
eval (Delta @@ Delta1 @@ Delta2, N, tcll v1_vv) ->
evalmtc Gamma M m1_v ->
evalma Gamma (p, tmtc ((pp,M,(dp,N)::sigma_v)::phi1_v), Delta, v)
((map (fun tpl => match tpl with
| (ttpl v11 v12) => map (fun t => let '(v1, (m1, Gamma1), p1) := t in (p1,m1,Gamma1,v1)) (zip3 [v11;v12] m1_v p1_v)
| v11 => map (fun t => let '(v1, (m1, Gamma1), p1) := t in (p1,m1,Gamma1,v1)) (zip3 [v11] m1_v p1_v)
end
) v1_vv)) empty.
最も重要なのはeval (Gamma, M, v)で「環境Gammaのもとで項Mが値vに評価される」という命題です。例えば、冒頭のプログラム中の(match-all [1 2] unordered-pair {[<pair $a $b> [a b]]})が{[1 2] [2 1]}に評価される、というのは次のようなCoqの命題として表すことができます。
Definition unordered_pair: tm :=
(tmtc [(pppair ppdol ppdol, ttplmtc tsm tsm,
[(dppair (dpvar "x") (dpvar "y"), (tcll [(ttpl (tvar "x") (tvar "y")); ttpl (tvar "y") (tvar "x")]))]);
(ppdol, tsm,
[(dpvar "tgt", tcll [tvar "tgt"])])]).
Definition match_all_example: tm :=
(tmal (tpair (tint 1) (tint 2)) unordered_pair (ppair (pvar "a") (pvar "b"),ttpl (tvar "a") (tvar "b"))).
Theorem unordered_pair_example : eval (empty, match_all_example, tcll [ttpl (tint 1) (tint 2);ttpl (tint 2) (tint 1)]).
また、Egisonの操作的意味論上でこの評価結果が正しいということも証明できます。 つまりunordered_pair_exampleを証明することができます。証明は長くなるので省略しますが、Egison.vの230行目以降にあります。
今後の課題
動機の項で述べたようにformalized-egisonの最終的な目標はEgisonの型安全性の証明です。Coqで表現すると
Theorem type_soundness: forall Gamma M T, is_typed Gamma M T => exists v, eval Gamma M v /\ is_typed Gamma v T.
となります。ただしis_typed Gamma M Tは環境Gammaの下で項Mに型Tがつくと読みます。
今後はtyped-egisonの型付け規則をCoqで書き直してis_typedを定義した上でtype_soundnessを証明する予定です。
MNISTを可能な限り高速に分類する
· 元記事
概要
- MNISTの分類をする学習済みモデルを軽量化し、
- 更にSIMD命令を使った高速化を行うことで、
onnxruntimeより高速なMNISTの分類が可能になりました。(シングルスレッドで)
前回までのあらすじ
この記事は前回の続きです。 前回はMNISTを分類する学習済みニューラルネットワークから不要な枝を削除し、軽量化した学習済みモデルを走らせる専用のランタイムsonnxを作ってMNIST分類の高速化を試みました。 今回はSIMD命令とマルチスレッド化による最適化でMNIST分類速度の限界に挑みます。
今回の目標タイムは既存のONNXランタイムonnxruntimeです。 この記事の各実行時間の計測は10回行い、その平均と分散を求めました。 onnxruntimeと最適化無しのsonnxの実行時間は、OS: Ubuntu19.04、CPU: Core i7 8th Gen、 メモリ: 16GiB、GPU無しの環境下で以下のようになりました。1
| 手法 | 実行時間 |
|---|---|
| onnxruntime(シングルスレッド) | 1.259秒 (標準偏差 0.1148秒) |
| onnxruntime(マルチスレッド) | 0.505秒 (標準偏差 0.04249秒) |
| sonnx(最適化無し) | 6.968秒 (標準偏差 0.08912秒) |
機械学習の推論過程、特にMNISTを分類する場合、においてボトルネックになるのは重み行列を入力ベクトルに乗算する処理です。 前回は重み行列の中で絶対値の小さい要素を無視することで計算の効率化を図り、行列の要素の80%を無視して約5倍の高速化に成功しました。 しかし、80%を削減してもなお乗算はボトルネックでした。
具体的にはsonnx.cppのこの部分がボトルネックになります。
for(int i=0;i<n;i++){ ret[B_row[i]] += B_scale[i] * x[B_column[i]]; }
SIMDによる高速化
まず、sonnxをSIMDで高速化してみます。 Core i7 8th GenではAVX2命令セットが使えるので256bitの演算を一度に行うことができ、 今回は32bit浮動小数点数で計算しているので最大8倍の高速化が見込めます。
しかし、元のコードはメモリへの間接参照を2つ(ret[B_row[i]]とx[B_column[i]])含んでおりそのままではSIMD化するのが困難です。
まずret[B_row[i]]への書き込みは同じB_row[i]の値を持つものをまとめて計算し、メモリへの書き込みを一度に行います。
x[B_column[i]]からの読み出しはAVX2のgather命令を使ってSIMD化します。
完成したコードがこれです。
float r = 0; __m256 vr = _mm256_setzero_ps(); for(cur;cur<n1;cur+=8){ __m256i vc = _mm256_loadu_si256((__m256i*)(&B_column_p[cur])); __m256 vx = _mm256_i32gather_ps(x_p, vc, 4); __m256 vs = _mm256_load_ps(&B_scale_p[cur]); vr = _mm256_fmadd_ps(vs, vx, vr); } for(cur;cur<n;cur++){ r += B_scale_p[cur] * x_p[B_column_p[cur]]; } __attribute__((aligned(32))) float t[8] = {0}; _mm256_store_ps(t, vr); ret[B_row_p[cur-1]] += r + t[0] + t[1] + t[2] + t[3] + t[4] + t[5] + t[6] + t[7];
実行時間はこんな感じになりました。
| 手法 | 実行時間 |
|---|---|
| sonnx(SIMD) | 1.121秒(標準偏差 0.02271秒) |
| onnxruntime(シングルスレッド) | 1.259秒(標準偏差 0.1148秒) |
ウェルチのt検定を用いて検定すると2有意水準5%で帰無仮説が棄却され、確かにonnxruntimeより高速に推論できています。 onnxruntime(シングルスレッド)と大きな差がつかないのはgather命令が遅いからでしょうか。
マルチスレッドによる高速化
(SIMDなしの)マルチスレッド化はどうでしょうか。
オリジナルのコードはメモリへの書き込みが一箇所(ret[B_row[i]]への書き込み)しかないので、この部分だけ複数スレッドで同時に書き込まないようにすればデータレースは起こりません。
スレッド数は手元で最適なものを探索した結果4にしています。
ソースコードはここにあります。
void CompressedGemm::calc_partially(const int index, const vector<float> &x){ int m = B_nrows_threads[index].size(); int cur = 0; for(int i=0;i<m;i++){ int n = B_nrows_threads[index][i]; float r = 0; for(int j=cur;j<cur+n;j++){ r += B_scale_threads[index][j] * x[B_column_threads[index][j]]; } ret[B_row_threads[index][cur]] += r; cur += n; } } vector<float> CompressedGemm::calc(const vector<float> &x){ ret = C; vector<thread> ths; for(int i=0;i<n_thread;i++){ ths.push_back(thread(&CompressedGemm::calc_partially, this, i, x)); } for(int i=0;i<n_thread;i++){ ths[i].join(); } return ret; }
結果はこんな感じです。
| 手法 | 実行時間 |
|---|---|
| sonnx(マルチスレッド) | 1.995秒(標準偏差 0.03405秒) |
| onnxruntime(マルチスレッド) | 0.5048秒(標準偏差 0.04249秒) |
完敗です。
SIMDとマルチスレッドの併用
では最後にSIMDとマルチスレッディングを併用してみます。 ソースコードはここにあります。
| 手法 | 実行時間 |
|---|---|
| sonnx(SIMD+マルチスレッド) | 1.358秒(標準偏差 0.05762秒) |
| onnxruntime(マルチスレッド) | 0.5048秒(標準偏差 0.04249秒) |
あまり効果がありませんね...複数コアからの結果をまとめるのに時間がかかっているのかもしれません。
まとめ
影響力の小さい要素を削除して学習済みのモデルを軽量化した上でSIMDとマルチスレッド化を用いて推論の高速化を試みました。 マルチスレッド環境ではonnxruntimeに勝てませんでしたが、シングルスレッドではonnxruntimeより高速に推論できました。
参考
WebAssemblyで自作言語用のGCを書く
· 元記事
前回の続きです。
概要
前回の記事ではWebAssemblyを出力するMLのサブセットコンパイラを作りました。しかし、WebAssemblyにはGabage Collection (GC) が未だに実装されていないため(2019/7/8時点)、メモリ管理は全て自分で行う必要があります。前回は超適当mallocで間に合わせていたのですが、今回はmalloc, freeを含むGCをWebAssembly(手書き)で作りました。
GCを含むMLコンパイラのソースコードはここで、GC本体はここです。
WebAssemblyの基礎知識
WebAssemblyはスタックマシンで実行される言語であり、各命令は引数をスタックのトップから取り出し結果をスタックのトップに積みます。トップ以外のスタックの中身を自由に見ることはできません1。また、スタックの他にもリニアメモリも持ち、i32.store, i32.loadといった命令でアクセスすることができます。リニアメモリは以下のような特徴を持ちます。
- アドレスは0から始まる
- アドレスは32bit
- 整数又は浮動小数点数をstore/loadできる
GCの実装
GCを書く前にまずmallocとfreeが必要です。今回はfree listでブロックを管理する簡単なものを書きました。各ブロックは下図のような構造になっていて、次のブロックのアドレス、ブロックサイズ、フラグ、確保したメモリを保持します。フラグにはfree listの終端かどうかの情報とブロックが使用中かどうかの情報が入っています。それぞれ4byteずつ使うのでmallocのためのヘッダだけで12byteも使います。贅沢ですね。
+-------------------------------------------------------+ | next block address | block size | flags | contents | | 4bytes | 4bytes | 4bytes | 4*n bytes | +-------------------------------------------------------+
mallocはfree listを先頭から走査して要求されたサイズ以上の空きブロックがあれば確保、最後まで走査して該当するブロックがなければ末尾に新たにブロックを作ります。freeは指定されたブロックの状態を空きブロックに変えるだけです。空きブロック同士を結合する処理はめんどくさいのでやっていません。
このmallocとfreeを使ってGCを作ります。『ガベージコレクション』によれば、GCには次の3種類があります。
- マーク・アンド・スイープ
- コピーGC
- 参照カウント
このうち、マーク・アンド・スイープとコピーGCは生きているオブジェクトを探索するのにルートセットが必要です。ルートセットは一般にレジスタ、スタック、グローバル変数から構成されます。ところが、WebAssemblyではスタックの中身を自由に見ることができないためルートセットが必要なGCは採用できません2。このため今回は参照カウント方式でGCを実装しました。
gc_malloc を呼び出すとmallocが指定されたサイズ+GCのヘッダサイズ分のメモリをリニアメモリ上に確保し、GCの管理情報を書き込んだ上でそのアドレスを返します。GCの管理領域は下図のようになっていて、確保したメモリのサイズ、参照カウントの値、フラグ、オブジェクトのためのメモリ領域から構成されています。フラグには参照カウント時に行う深さ優先探索用のビットと格納しているオブジェクトが値(整数値又は浮動小数点数)か否かの情報が含まれます。
+----------------------------------------------------+ | memory size | reference count | flags | contents | | 4bytes | 4bytes | 4bytes | 4*n bytes | +----------------------------------------------------+
これがmallocのcontentsの中に入っているので実際にはこのようになります。
+---------------+------------+--------+--------------------------------------------------------+ | next malloc | malloc | malloc | malloc contents | | block address | block size | flags | +----------------------------------------------------+ | | 4bytes | 4bytes | 4bytes | | memory size | reference count | flags | contents | | | | | | | 4bytes | 4bytes | 4bytes | 4*n bytes | | | | | | +----------------------------------------------------+ | +---------------+------------+--------+--------------------------------------------------------+
参照カウントはコンパイラが変数のスコープに応じてオブジェクトの参照カウントを増減させるコード(gc_increase_rc, gc_decrease_rc)を挿入することで操作します。基本的にオブジェクトの作成時に参照カウントが1増加し、そのオブジェクトを指す変数のスコープが終了したところで参照カウントが1減らします。参照カウントが0になるとそのオブジェクトは解放され、またそのオブジェクトが保持していたアドレスが指すオブジェクトの参照カウントを1減らします。この操作は再帰的に行われます。ただし、解放されたオブジェクトが値だった場合はこの再帰的な操作は行われません。
↓gc_increase_rc, gc_decrease_rcはこんな感じで挿入される
(i32.const 1) (i32.store))) (; let fun_fib_0 end ;) (call $gc_increase_rc) (get_local $val_b_6) (call $gc_decrease_rc) (drop) (get_local $val_a_5) (call $gc_decrease_rc) (drop)
基本的に参照カウントの操作は変数のスコープと連動させればよいのですが、関数終了時には特殊な処理が必要になります。関数内のローカル変数が指すオブジェクトは関数の終端で解放されますが、関数の戻り値が開放されるオブジェクトを含む場合、すでに解放されたオブジェクトが関数の戻り値に含まれることになります。このようなバグを防ぐためには、関数内のローカル変数を開放する処理を行う前に戻り値オブジェクトの参照カウントをインクリメントしておく必要があります。
その他にも配列の要素を書き換えるときは前のオブジェクトの参照カウントを減らしてから、新しく代入するオブジェクトの参照カウントをインクリメントする必要があるなど、細かいところでいろいろハマりました。
結果
フィボナッチ数列を第10項まで計算するプログラムでプログラム終了時のメモリ使用量をGC有りの場合と無しの場合で結果を比較してみると、以下のようになりました。使用メモリの4分の3ぐらいを解放できているのでそれなりに(僕が)満足できる結果です。
| - | メモリ使用量 |
|---|---|
| GCなし | 22868byte |
| GCあり | 4660byte |
let rec fib n = if n < 3 then 1 else let a = fib (n - 1) in let b = fib (n - 2) in a + b in let rec print x = if 10 < x then 1 else let a = fib x in print_i32 a; print (x+1) in print 0
参考リンク
- WebAssemblyでGCを実装する
GCの管理領域の設計はほとんどこの記事のものをそのまま使っています。 - WebAssemblyでGC
bitmap式のMark and SweepのGCを書く話。
余談
ONNXファイルから不要な枝を削ってMNISTの推論を高速化してみる
· 元記事
この記事の中のソースコードは全てhttps://github.com/akawashiro/sonnxにあります。
概要
- ニューラルネットワークから要らなそうな枝を80%削除しても精度が変わらなかった
- ONNXの中身をいじるのが大変だった
- onnxruntimeには勝てなかった
背景
機械学習の学習済みモデルを小さなデバイスで動かす、というのが最近流行っているそうです。機械学習では、学習には大きな計算コストがかかりますが、推論はそれほど大きな計算コストがかかりません。このため、学習だけを別のコンピュータで行っておいて、実際の推論は小さなデバイスで行うということが可能です。
ただし、推論だけでもそれなりに計算資源が必要です。そこで、学習済みのモデルの高速化が重要になります。Raspberry Piに搭載されているGPUを使うIdeinとか有名です。
僕も学習済みモデルの推論を高速化できそうな方法を思いついたので実験してみました。
アイデア
今回はMNISTを分類する学習済みモデルを高速化します。今回使用するモデルは次の図のようなものです。画像は28*28(=784)pxなので入力は784個、出力は各数字の確率なので10個あり、中間層が2つ挟まっています。各層間は全結合しており、活性化関数としてReluを使います。

このモデルを教師データを使って学習すると、枝の重みが変わってこんな感じになります。

僕のアイデアは学習後のネットワークから重みの小さい枝を取り去ってもちゃんと動くんじゃないか、というものです。重みの小さい枝を取り去るとこんな感じになります。

枝の本数が少なくなれば、推論を高速化できそうな気がします。
アイデアの裏付け
実際に学習後のモデルで赤丸で囲った部分の重みの分布を確認します。

分布はこのようになっています。

重みが0の部分が非常に多いです。まず、学習済みモデルから重みが0の枝を削除しても推論結果に影響しないはずです。また、グラフが左右対称になっているので、絶対値の小さい順に削除していけば、各パーセプトロンへの入力はそれほど変化しない気がします。
手法
やることは非常に単純です。ニューラルネットワーク中の各層間枝の重みの統計を取り、重み上位何%かを残して残りを削除します。つまり、何らかの方法で学習済みのモデルから枝の重みのデータを取り出し、枝をカットし、さらに加工後のモデルのデータを使って推論できるようにします。
幸い今はONNXという良いものがあります。ONNXとは学習済みモデルのデータを出力する形式の一つで、多くのフレームワークが対応しています。
今回はChainerで書いたモデルからONNXデータを出力し、そのデータを加工することにしました。また加工後のデータはC++で書いた俺俺ONNXランタイムに実行してもらうことにしました。
纏めると、
1. Chainerでニューラルネットワークを書いて学習する
2. 学習済みのニューラルネットワークからONNXデータを出力する
3. ONNXデータを俺俺ONNXランタイムに読み込んで加工、実行する
となります。一つづつ何をやるのかを説明します。
1. Chainerでニューラルネットワークを書いて学習する
2. 学習済みのニューラルネットワークからONNXデータを出力する
1と2は簡単です。onnx-chainerを使えばすぐにできます。
python3 learn_mnist.py
でmnist.onnxというファイルができます。
3. ONNXデータを俺俺ONNXランタイムに読み込んで加工、実行する
ここが大変でした。ディープラーニングフレームワークからのONNXモデルの出力は多くの人が試しているのですが、出力したONNXモデルをチューニングしようとする人はほとんどいないようです。
3.1 ONNXデータを解析する
とりあえずnetronというONNXの可視化ツールでmnist.onnxを可視化してみました。

GemmはGeneral matrix multiplyの略です。各Gemmノードは行列BとベクトルCを持ち、ベクトルxを入力としてBx+Cを出力します。Reluは活性化関数です。
Reluはmax(0,x)で定義されている関数のでONNXから抽出する必要は無く、各Gemmノードの行列BとベクトルCの情報だけをを抽出できれば良いです。
今回は各GemmノードのBとCをテキストファイルとして抽出します。
> python3 analyze_mnist_onnx.py
は次のファイルを出力します。
*************_matrix.txt
mnist.onnxの全てのGemmノードのB行列とC行列*************_matrix.png
各行列の中の重みの分布mnist.onnx.json
ONNXファイルをJSONに変換したものmnist_train.txtmnist_test.txt
MNISTをC++から読み込みやすくするためにテキストファイルに変換したもの
各*************_matrix.txtがどのGemmノードに対応するのかはmnist.onnx.jsonを睨むとわかります(←ここ超不親切)。
3.2 抽出したデータを加工、実行する
g++ -O3 -mtune=native -march=native -mfpmath=both sonnx.cpp && ./a.out
で出力した重みのデータを読み込み、推論を実行します。sonnx.cppは簡単なONNXランタイムになっており、mnist.onnxから抽出した行列のデータとMNISTの画像データのテキストファイルから推論を行います。デフォルトではmnist_test.txtの10000枚について推論を行います。
出力はこのようになります。
accuracy, time 0.9817000031, 15.93887043 compress_ratio, accuracy, time 0, 0.9817000031, 36.66616821 0.05, 0.9817000031, 34.61940384 0.1, 0.9818000197, 32.64707184 0.15, 0.9818000197, 30.78090286 0.2, 0.9817000031, 29.01914406 ..........................
2行目は読み込んだデータを加工せずで隣接行列表現で保持したときの推論の精度と計算時間、4行目以降は読み込んだデータを加工したときの推論の圧縮率(compress_ratio)、精度と計算時間です。圧縮率が0のときは全く加工しないのと同じ、圧縮率が0.3のときは枝の重みの絶対値が小さい30%を削除したときの結果です。圧縮率が1になると全ての枝が削除されます。
データを加工して枝を削除するとき、sonnx.cppでは行列データを非零成分のインデックスとその値の組の配列として保持しています。これは枝を削除したときに保持するデータ量が減らし、高速に計算するためです。
結果
圧縮率を変化させたときの精度と計算時間のグラフです。圧縮率を0.8まで上げても推論の精度が変わっていません。これは学習済みモデル中の8割の枝を削除しても、推論精度が保てるということです。驚きですね!

一方、計算時間はほぼ枝の数に反比例しています。これは予想通りでした。

表にしてみます。推論精度を1%犠牲にするだけで2倍も高速化できています。やったね!
| 圧縮率 | 推論精度 | 計算時間(秒) |
|---|---|---|
| 0 | 0.981 | 15.9 |
| 0.8 | 0.971 | 7.04 |
※ 圧縮率0のときの値は重みのデータを隣接行列表現で保持したときのものです。
おまけ
最後にonnxruntimeで元の学習済みモデルを実行したときの時間を計ってみます。
> python3 analyze_mnist_onnx.py Accuracy rate = 0.9817 , time = 3.285153865814209
え、3秒?? 普通に考えると16秒ぐらいになるはずです。なぜこんなに速いんだ...
理由は色々あると思いますが、僕が思いつくのは以下の2つです。
- AVXなどのSIMD拡張命令を使っている
(僕もAVXを使おうとしましたが、scatter命令が僕のPCで使えないので諦めました。) - CPUのキャッシュに乗るようなプログラムになっている
関連記事・研究
- 深層学習のモデル圧縮・高速化に関する論文80本ノック
学習済みのモデルを圧縮する論文がまとめられています。このブログ記事の内容はリンク先の「スパースなモデル」の章に該当します。 - モデルアーキテクチャ観点からのDeep Neural Network高速化
DNNの高速化に関するスライドです。Pruningの章がこのブログ記事に該当します。 - Reducing the Model Order of Deep Neural Networks Using Information Theory
上のリンク中の論文で特にこの記事に近いものです。このブログ記事でやっているのは、この論文のmagnitude-based pruningです。 - Deep Compression: Compressing Deep Neural Networks with Pruning, Trained Quantization and Huffman Coding
CPUでの実行速度もオリジナルのモデルより速くなってるっぽくてすごい。何をどう実装したのか知りたい。 - keras-surgeon
kerasのモデルをpruningするためのソフトウェア。
まとめ
- ONNXランタイムを自作したらナイーブな実装の二倍の速度で推論ができた
onnxruntimeには勝てなかった
高階関数を基本的な関数の合成で作った関数でQuickCheckする
· 元記事
高階関数をQuickCheckでテストしてみる
QuickCheckを知っていますか?
QuickCheckと言うのはHaskellのデータ駆動型のテスト用ライブラリで
テストしたい関数を指定するとその引数に合わせて適当なテストデータを生成してくれます。
では高階関数(関数を引数に取る関数)をQuickCheckでテストすると
どんなテストデータ(関数)を生成してくれるのでしょうか。
ちょっと確認してみましょう。
-- QCTest.hs import Test.QuickCheck import Test.QuickCheck.Function prop :: Fun Integer Integer -> Bool prop f = apply f 10 == apply f 20 main = quickCheck prop
% stack runghc QCTest.hs
*** Failed! Falsifiable (after 2 tests and 12 shrinks):
{20->0, _->1}
...なんだこの関数は?
入力と出力の組が列挙されてるだけやんけ。
僕の思ってる関数と違うんですが。
map (+2)とかtailとかそういうやつを生成してほしいなぁ。
まともなテスト用関数を生成する
QuickCheckのテストデータが気に入らないので、まともな(主観)テストデータを生成してみました。
今回生成するテストデータは[Int] -> [Int]の関数に限定します。
そもそも関数を生成するってどうやればいいんでしょうか?
QuickCheckのように入力と出力の対を列挙するのは筋が悪い気がします。
mapや(+)、tailといった基本的な関数を合成して関数を生成したいのです。
我々がプログラミングするときってそうやりますよね。
入力と出力の組を列挙する人はあんまりいないと思います。
[Int] -> [Int]型の関数を列挙してみます。
tail reverse map (* 10) map (+ 10)
tailやreverseはそもそも[Int] -> [Int] 型です。
mapは(Int -> Int) -> [Int] -> [Int] 型なので
(+ 10) :: Int -> Intを渡すと[Int] -> [Int]型になります。
こんな風にどの関数(例 map)にどの関数(例 (+ 10))を適応すれば
どんな型(例 [Int] -> [Int])になるかを規則として書き出します。
型付け規則っぽく書くとこんな感じです。
ちょっとわかりにくいかも知れません。
[Int] -> [Int]型の関数を2つ作ってみました。
左はmap (+ 10)のような関数、右はmap (+ 10 (* 5))のような関数を表しています。
一番下の型にある関数の規則を持ってきて、上に必要な型を書いて
さらにその必要な型をもつ関数の規則を持ってきて...という感じです。
とりあえずこの規則を使って関数を生成できるようになりました。
[Int] -> [Int]型の関数を生成した例がこちらです。
% stack exec qcfun-exe > Test datum are following. > ["tail","reverse","(map (* 75))","tail","reverse"] ...
今見せた例はこのようなデータ型で表現されています。
data QProg = QMap1 QProg | QMap2 QProg QProg | QTail1 QProg | QTail | QRev | QMult QProg | QAdd QProg | QRand Int deriving (Eq) instance Show QProg where show (QMap1 p) = "(map " ++ show p ++ ")" show (QMap2 p1 p2) = "(map " ++ show p1 ++ " " ++ show p2 ++ ")" show (QTail1 p) = "(tail " ++ show p ++ ")" show (QTail) = "tail" show QRev = "reverse" show (QMult p) = "(* " ++ show p ++ ")" show (QAdd p) = "(* " ++ show p ++ ")" show (QRand i) = show i
QProg型がプログラム(関数)を表す型です。
このQProg型に対して適当なShowインスタンスを定義してあげると関数っぽく見えるようになります。
作ったテストデータ用の関数で高階関数をテストする
次に作った関数を使って高階関数をテストしてみましょう。
今回テストする対象のプログラムはこれです。
func f = reverse . f prop f = (func f [1,2,3]) == [1,2,3]
関数fを引数に取る簡単な高階関数funcの性質をテストします。
しかし作ったテストデータ用の関数はすべてQProg型です。
つまりQProg型をなんとかして[Int] -> [Int]型に変換したうえで
テストしたい高階関数に適用する必要があります。
今回はTemplate Haskellを使ってQProg型を[Int]->[Int]型のHaskellの関数に変換しました。
Template Haskellというのはプログラムの中でプログラムを作るためのツールです。
Lensなんかで使われています。
動作例がこちらです。
% stack exec qcfun-exe ... Test results are following. [(False,"tail"),(True,"reverse"),(False,"(map (* 75))"),(False,"tail"),(True,"reverse")]
テストデータは先頭から順に["tail","reverse","(map (* 75))","tail","reverse"]だったので正常に動いているようです。
まとめ
高階関数をまともな(主観)関数でテストできるようにしました。
しかし技術的な制約のためテストデータをコンパイル時に生成するので
テストデータを完全にランダムに生成することはできませんでした。
参考
http://haskell.g.hatena.ne.jp/mr_konn/20111218/1324220725
付録
githubへのリンクです
https://github.com/akawashiro/qcfun
以下のコマンドで実行できます。
git clone https://github.com/akawashiro/qcfun.git stack build stack exec qcfun-exe
WebAssemblyを出力するMinCamlコンパイラを実装しました
· 元記事
概要
WebAssemblyを出力するMinCamlコンパイラml2wasmをフルスクラッチで実装しました。
github.com
マンデルブロ集合を計算するこんな↓感じのMinCamlのソースコードが

こんな↓感じのWebAssemblyに変換されて

実行して、適切にプロットするとこんな↓感じになります。

導入
WebAssemblyとは
WebAssemblyとはブラウザで実行可能な低級プログラミング言語であり、仮想的なスタックマシン上で動作します。 また、JavaScriptに比べて構文解析が容易であり高速に動作します。 WebAssemblyは最近のほとんどのブラウザで動かすことができます。*1
;; WebAssembly のテキスト表現の例 (module (func (export "add") (result i32) (i32.add (i32.const 1) (i32.const 3))))
MinCamlとは
MinCamlとは東北大学の住井先生*2が設計したプログラミング言語MLのサブセットです。 言語仕様が小さく、処理系を制作するのが比較的容易なのが特徴です。 住井先生は未踏事業としてMinCamlのコンパイラも制作されています。 このコンパイラは読みやすくドキュメントもしっかりしているので、コンパイラを作ってみたい人には非常にオススメです。 ml2wasmの開発でもこのコンパイラを大いに参考にしました。
(* MinCaml の例 *) let rec add = 1 + 3 in add
コンパイラの作成
今回はMinCamlのソースコードをWebAssemblyのテキスト表現に変換するコンパイラml2wasmを作成しました。 基本的にml2wasmはオリジナルのMinCamlコンパイラと同じ構造ですが、WebAssemblyがスタックマシンであることを利用していくつかの変換の工程が省略されています。
ml2wasmの実装の詳細
ml2wasmでは次の5段階の工程でMinCamlをWebAssemblyに変換します。
特に説明のない部分はオリジナルのMinCamlコンパイラと同じです。 今回はHaskellで実装しましたが、もともとの実装言語がOCamlでHaskellとそれほど変わらないので、その点は苦労しませんでした。 以下、WebAssembly特有のコンパイラ実装事情をいくつか説明します。
オリジナルのMinCamlコンパイラには3と4の間にK正規化、4と5の間にレジスタ割当という処理がありましたが、ml2wasmではこれら2つの処理を省略しています。 K正規化はWebAssemblyでは命令のオペランドとしてスタックの先頭の値を使い、オペランドをレジスタに格納する必要がないため、 レジスタ割当はWebAssemblyにはレジスタが存在しないため、それぞれ省略できました。
逆に、WebAssembly特有の難しい工程としてはコード生成があります。
通常、関数型言語のコンパイラではプログラムをクロージャ変換し、関数から自由変数を削除します。
その後コード生成の工程で関数呼び出しは間接ジャンプ(MIPSならjr命令)に変換されます。
この間接ジャンプをWebAssemblyではcall_indirect命令で実現します。
;; call_indirect命令はこんな感じで呼び出される。 ... (i32.load) (call_indirect (param f32) (param f32) (param i32) (result i32))))) ... ;; (param f32) (param f32) (param i32) (result i32) ← 型が必要
ここで問題になるのがcall_indirect命令が呼び出す関数の型をオペランドとして要求する点です。
これはクロージャ変換後の中間言語に型情報を残しておく必要があることを意味します。
コンパイラを書き始めたときにはこのことに気づかなかったので、あとから型情報を追加するのに苦労しました。
他には公式のリファレンスがどこにあるのかイマイチわからない問題もあります。 結局これを使ったのですが、 URLに公式感がなく最後までこのリファレンスが公式のものなのかはわからずじまいでした。
結果
マンデルブロ集合を出力するプログラムをコンパイルできるようになりました! やったね!
マンデルブロ集合を出力するMinCamlのソースコード
mandelbrot.ml · GitHub
コンパイル結果
mandelbrot.wast · GitHub
ちなみにマンデルブロ集合っていうのはこんな↓やつです。

今回作ったコンパイラでコンパイルし実行した結果(を適切にプロットしたもの)は↓

まあ大体あってますね。点の数が以上に少ないのは大量の点をプロットしようとするとすぐにメモリ不足に陥るからです。
今後の課題
ml2wasmで出力したWebAssemblyコードはすぐにメモリ不足に陥ります。 理由は単純でGCがないからです。 WebAssemblyでGCを実装する試みとしては κeenさんのWebAssemblyでGC | κeenのHappy Hacκing Blog がありますが、今回はめんどくさいのでメモリは確保しっぱなしです。
そのうちWebAssemblyにデフォルトでGCが入るとかいう噂も聞くので、そのときにGCを入れればいいやと思っています。
参考リンク
- WebAssembly
WebAssemblyの公式ページ - 速攻MinCamlコンパイラ概説
MinCaml - WebAssemblyでGC | κeenのHappy Hacκing Blog
κeenさんによるWebAssemblyでのGCの実装 - wasm-reference-manual/WebAssembly.md at master · sunfishcode/wasm-reference-manual · GitHub
WebAssemblyの仕様書(?)。多分公式ではないが一番役にたった
*1:https://github.com/WebAssembly/wabtを使うとコマンドラインでWebAssemblyを試せます。
*2:設計した当時はペンシルバニア大学に所属していたようです 美しい日本のMLコンパイラ – 未踏iPedia -まだ誰も踏み入れたことのない世界へ-
ネットワークデバイスドライバを一からビルドしてインストールした
· 元記事
概要
新し目のコンピュータにDebianを入れたら、ネットワークデバイスドライバが入ってなくて結構大変だった。
経緯
最近、自分の使っていたコンピュータ(6年前くらいのデスクトップ)に限界を感じ始めたので、日本橋で新しいものを買ってくることにした。購入に当たっては、換装が難しいCPUをCorei7 8700kに決定し、フィーリングで選ぶことにした。日本橋を端から端まで歩き回って、結局LM-iH700XD1-EX4を買った。

Windows10をDebian streachで上書きして起動すると、ネットワークドライバが入ってないことがわかった。マザーボードが新しすぎるのでドライバがDebianに取り込まれていないのだろう。こういう場合は自前でドライバをビルドする必要がある。
NIC(Network Interface Card)の型番を調べる
まず、NICの型番を調べる。商品のスペックを確認したが、「LAN:10/100/1000BASE-T LAN(オンボード)」としか書いていない。マザーボードの方には「マザーボード:Intel B360 Micro ATX LGA1151」と書いてあった。オンボードのNICなのだから、たぶん「Intel B360」のほうが関係しているのだろうと考えられる。ここから「Intel B360 network driver」などで検索して一時間ほど頑張ったが、なんの成果も得られなかった。
% lspci (省略) 00:1f.6 Ethernet controller: Intel Corporation Device 15bc (rev 10)
なるほど、「Intel Corporation Device 15bc linux driver」で検索すれば良さそうである。検索すると、Linux Kernel Driver DataBaseがトップに出てきた。「15bc 」でページ検索すると
vendor: 8086 ("Intel Corporation"), device: 15bc ("Ethernet Connection (7) I219-V")
が出てくる。「I219-V linux driver」で検索してみると「e1000e」の最新のドライバを入れればいいらしい?
そこで、もうどこで見たのか忘れたけど Intel Ethernet Drivers and Utilities の最新版をインストールすればいいらしい。(検索しすぎて記憶がないorz)
makeを入れる
というわけでe1000e-3.4.2.1.tar.gzをビルドする。 ネットワークに繋がらないので別のパソコンでファイルをダウンロードしてUSBメモリでコピーした。
展開して、make installすると...makeコマンドが入ってないと言われた。
Debianのインストール元のlive USBをマウント後、apt install build-essentialで入れた。
最初から入れといてほしい...
デバイスドライバをビルドする
e1000e-3.4.2.1.tar.gzを展開して、make installするとカーネルのヘッダファイルが足りないと言われた。
% uname -a Linux gley 4.9.0-8-amd64 #1 SMP Debian 4.9.110-3+deb9u4 (2018-08-21) x86_64 GNU/Linux
4.9.0-8-amd64のヘッダファイルが必要なようだ。別のパソコンで該当のdebファイルを落としてきてdpkg -iで入れる。ちなみにcommonとamd64の両方が必要だった。
その後、再度make install。
再起動するとネットワークに繋がっていた。
ネットワークデバイスドライバをインストールできた〜〜〜〜嬉しい〜〜〜〜
— a_kawashiro (@a_kawashiro) September 27, 2018
まとめ
新し目のパソコンでLinuxを動かすのは大変。 ただドライバをビルドしてインストールする作業は初めてで勉強になった。 もうやりたくない。
ちなみにオーディオデバイスドライバが入っていないので音は出ない。 そのうち頑張る。
このマザーボード新しすぎて音も出せないwww
— a_kawashiro (@a_kawashiro) September 27, 2018
プログラミング言語Egisonの型システムを設計するインターンをした
· 元記事
概要
2018年3月11日から4月14日まで楽天技術研究所でEgisonの型システムの設計 & 型検査器を書くインターンをしていた。 もうインターンから半年も経ってしまったが、何も書かないよりはましというわけでブログ記事を書くことにした。
実装した型検査器はここにある。 まだまだ不完全ではあるが、一応動くのでよかったら試してみてほしい。
Egisonとは
Egisonとは江木さんという方が開発しているLisp likeなプログラミング言語で、非常に強力なパターンマッチ機構を持つ言語である。 江木さんは2007年からほぼ一人でEgisonを開発し続けており、その歴史はここで見ることができる。 最近ではパターンマッチ機構を活かして、数式処理システムの制作に使われている(江木さんがつかっている)。
インターンが始まるまで
Twitterをぼんやり眺めていたら「Haskellで言語実装するバイトがある!」ということで即応募した。 DMを送ったらSkypeで面接することになって、そのまま採用してもらった。 普段は京都に住んでいるので、東京に行かずに済んだのはすごく助かった。楽天技術研究所では,現代数学を簡潔に記述するためのプログラミング言語EgisonをHaskellで開発する方(アルバイトやインターンなど)を募集しています.
— Egison (@Egison_Lang) 2017年11月13日
Haskellで言語実装することに興味ある方は,ぜひDMください!
採用が決まった後は、Egisonが微分幾何の記述に使われるということなので局面と曲線の微分幾何を読んでいた。 研究室でPPL2018に行ったら隣の席に江木さんがいて、そのとき初めて江木さんに会った。
インターン初週
インターン初日、東京で疲れてしまったのか、高熱と吐き気がして会社を休んだ。 社会人失格かと思ったが、次の日出社したら「大丈夫ですか」と気遣ってくれて嬉しかった。 後でTwitterを見た感じ、東京で風邪が流行っていたらしい。
インターン初週はEgisonの実装を読むことが主な業務だった。 Egisonは巨大な構文木インタープリタとして実装されており、相次ぐ機能の拡張によってエライことになっていることが分かった。
インターン2週目
江木さんがEgisonに型検査を入れたいというので、Egisonのサブセットを抽出、定式化をして、そのサブセットに対して型システムを設計しようという話になった。
Egisonにはmatcherやprimitive patternなどの通常の言語には無い構文要素があり、ちょっと大変だった。
この週の金曜日、江木さんが東大の出身研究室(萩谷研)を訪問するというのでくっついて行った。 萩谷研では、Egisonの意味論をどう定義するかという話をしていた(僕は議論する様子を眺める係をしていた)。 この議論は後々論文になり、APLAS 2018に採択されている。
インターン3週目
2週目に設計した型システムを型検査器として実装した。 基本的にはHM型の制約解消アルゴリズムで実装したが、 型付けできない式を受理するためにワイルドカード型を入れたりした(Egisonはもともと型なしの言語なので、型付けできないが動く式が基本ライブラリ中に存在する)。 当時は知らなかったのだが、あとで確認してみると漸進的型付けの型検査と同じような実装になっていた。
インターン4週目
引き続き型検査器を実装した。
matcherを正しく型付けできたときがとても嬉しかったのを覚えている。

↑インターン中の食事の一例
インターン5週目
成果発表のためのスライド制作&発表練習をした。 スライドが全直しになったりしていい経験になった。 この経験が後々、未踏の二次審査で役に立ったような気がする。 最終発表の後で江木さんと二人でもんじゃ焼きを食べにいった。
感想
インターンを通して基本的に江木さんと二人だけで仕事をしていたが、江木さんがとても良い方だったのであまり苦ではなかった。 また週に一度kotapikuさんや梅崎さんがアルバイトに来て、数学の話をしたりするのが楽しかった。 食堂も大変美味しいのでついつい食べすぎてしまい、かなり太った。
ISUCON8予選に参加した
· 元記事
ISUCON8の予選にokeigoさん、mayokoさんとチーム「しょラーさんのおかげ」として参加していた。 最終的な得点は8000点くらいで予選落ちだった。悔しい。
チームメイトの参加記
予選まで
とか言っていたらISUCON出てみたいけどその分野について全く知らない
— a_kawashiro (@a_kawashiro) July 15, 2018
としょらーさんの穴を埋める形で誘ってもらい、参加することになった。ご検討ください https://t.co/fOPEOwDecN
— しょラー (@shora_kujira16) July 15, 2018
ISUCONの参加が決定してからは手元でISUCON7の予選、ISUCON6の予選を立てて練習していた。 N+1クエリ問題について初めて知った。 あとsystemd、MySQL、nginx、pythonのプロファイラの設定も初めてでいろいろ勉強になった。
予選の一週間前にokeigoさんがISHOCON2をセットアップしてくれて、そのサーバを使って3人で練習した。 3人とも離れたところに住んでいるので、Discordで音声通話、Slackでチャット、GitHubでコードを共有することになった。 言語は3人とも読めるPythonを使うことにした。
予選当日
朝
6時ぐらいに目が覚めた。いつもより6時間くらい早起き。
10時〜
予選スタート。 設定ファイルやpythonのアプリの実装をデプロイするスクリプトを書いた。 h2oをnginxに切り替えるのはokeigoさんがやってくれた。 この間にmayokoさんがアプリを読んでいた。
11時〜
とりあえずベンチマークを取った。
/favicon.icoが異常に重かったのだが、okeigoさんが設定のtypoを見つけて直したら解消した。
13時〜
僕はapp.pyの中でsheetをランダムに並び替えているところを小手先の小技で高速化しようとしていた。
が、あまり早くならなかった。
mayokoさんとokeigoさんがapp.pyのget_eventの改善をやっていた。
14時〜
mayokoさんの実装が終わった。 マージしてベンチを取ったら5000点ぐらい出て、学生3位になったので正直勝ったと思った。
15時〜
okeigoさんがredisを使ってキャッシュを実装していた。 venv環境にどうやってredisをインストールするのかがわからずに悪戦苦闘した。 結局バイナリをコピーして対応したらしい。SUGOI
16時〜
キャッシュの実装が動いたので、そろそろ3台構成にしましょうか、という話になった。 が、ポートの開放とかでいろいろ詰まった。 僕はあんまり何もできなかった。
17時〜
17:30ぐらいに3台構成が動いた。 あとは何度がベンチマークをとって終了。 最終的には7900点ぐらいでたはず。
18時〜
お祈りした。
— a_kawashiro (@a_kawashiro) September 16, 2018
19時ごろ
結果が出た。予選を通過することはできなかった。
燃え尽きたので起き上がれない
— a_kawashiro (@a_kawashiro) September 16, 2018
反省
- okeigoさんの負担が重すぎた。もうちょっとサーバの設定とか練習しとけばよかった。
- 役割分担がきちんと決まっていなかった。アプリ2人、インフラ1人というのがバランスが良さそうだった。
- 複数台構成におけるサーバ間の通信についてわかっていなかった。ISUCONのような特殊な環境ではファイヤーウォールを切って、全サービスが
0.0.0.0:portをリッスンしてしまえば良いと思った。 - サービスとして登録するpythonスクリプトに新しくライブラリ(
redis)を追加する処理についてわかっていなかった。 - プロファイルをちゃんと見ていなかった。ログの可視化スクリプトを作成しておくべきだった。
まとめ
チカラが足りなかった。悔しい。
Vimで初めてコマンドを定義してみた
· 元記事
書かないと忘れちゃうから…
これだけ
:command! MT :MerlinTypeOf :MT
Vimをコンパイルしてインストールする
· 元記事
使っているプラグインの関係で+python,+lua,+perlなVimが必要になりました。
しかし、Ubuntuのパッケージで条件を満たすものが見当たりません。
そこで自分で一からコンパイルしてインストールしました。
僕は自分でコンパイルしたソフトウェアを$HOME/.local/以下にインストールしているので、
--prefix=$HOME/.localとしています。
必要に応じて適当に変更してください。
git clone https://github.com/vim/vim cd vim ./configure --prefix=$HOME/.local/ --with-features=huge --enable-multibyte --enable-gpm --enable-cscope --enable-fail-if-missing --enable-perlinterp --enable-python3interp --enable-rubyinterp make install
あとは$HOME/.local/bin/vimにパスを通すだけです。
nvimに移行したほうがいいのかなぁ。
githubのバックアップを取る
· 元記事
年末も近くなってきたのでgithubが落ちた時に備えてリポジトリのバックアップを取ることにしました。
以下のスクリプトを実行するとgithub以下にすべてのリポジトリがバックアップされます。
YOUR_ACCESS_TOKENはSign in to GitHub · GitHubで作れます。
Select scopesのところでreposの欄をすべてチェックしてトークンを作成してください。
#! /bin/bash token=YOUR_ACCESS_TOKEN curl "https://api.github.com/user/repos?access_token=${token}&per_page=100&affiliation=owner" 2>/dev/null -o tmp repos=`cat tmp | grep ssh_url | cut -d" " -f 6 | sed -e 's/"//g' | sed -e 's/,//g'` mkdir github cd github for r in ${repos}; do name=`echo ${r} | sed -e "s:git.*/::" -e "s:[.]git::"` git clone ${r} cd ${name} for branch in `git branch -a | grep remotes | grep -v HEAD | grep -v master `; do git branch --track ${branch#remotes/origin/} $branch done cd .. done rm tmp
これでgithubが落ちても安心ですね。
Freeモナドについて今更勉強してみた
· 元記事
5年ほど前、Freeモナドという言葉がHaskell界隈でよく見られました。
僕のフォローしている人たちが次々に解説記事をアップしていたように思います。
5年たった今になって、Freeモナドについて興味が湧いてきたので記事を書いてみることにしました。
Freeモナドの定義に行く前にFree型について説明します。
-- The kind of f is * -> * like Maybe, []. -- The kind of a is * like Int, Char, [Char]. data Free f a = Pure a | Free (f (Free f a))
ここでfはカインド * -> * を持つ型コンストラクタであり、あとで述べるようにFunctorのインスタンスです。
aはカインド * のただの型です。
fが[]、aがIntの場合について調べてみましょう。
free1 :: Free [] Int free1 = Pure 10 free2 :: Free [] Int free2 = Free [Free [Free [Free []]]] free3 :: Free [] Int free3 = Free [Pure 10]
これら3つの定数関数はすべてFree [] aの型を持ちます。
直感的に言うとリストもリストのリストのリストもただの整数もすべて同じ型になっています。
これだけではFree型をどう使っていいのかわかりません。
これをモナドにしてみましょう。
-- functor is typeclass which "fmap" is defined -- (<$>) :: Functor f => (a->b) -> f a -> f b -- (<$>) = fmap instance Functor f => Functor (Free f) where fmap f (Pure a) = Pure (f a) fmap f (Free fa) = Free (fmap (fmap f) fa) instance Functor f => Monad (Free f) where return = Pure Pure a >>= k = k a Free fm >>= k = Free (fmap (>>= k) fm)
モナドになると何が良いのでしょうか?
簡単なゲームを作る例を見ながら、Freeモナドの利点を確認しましょう。
このゲームは
- 入力を受け取る
- 文字列を出力する
- 一秒待つ
の3つの要素の組み合わせで構成されています。
ゲームを表すデータ型がこれです。
data Game cont = GetInput (String -> cont) | Render String cont | Wait cont instance Functor Game where fmap f (GetInput g) = GetInput (f . g) fmap f (Render str cont) = Render str (f cont) fmap f (Wait cont) = Wait (f cont)
各コンストラクタは動作を行ったあとの続きの計算を持ちます。
例えば、GetInput fだったら入力を受け取ってfに渡すことで
続きの計算contを計算し、contを実行します。
ここでGameデータ型をFreeモナドと組み合わせて使う利点を説明します。
game1 :: Free Game () game1 = Free (Wait (Free (Render "Hello" (Pure ())))) game1' :: Free Game () game1' = (Free (Wait (Pure ()))) >> (Free (Render "Hello" (Pure ())))
一秒待って"Hello"と出力するゲームを2つの方法で記述してみました。
game1は愚直な記述、game1'は>>演算子を用いた記述です。
実はこの2つは全く同じものです。
game1'を定義にしたがって展開してみます。
(Free (Wait (Pure ()))) >> (Free (Render "Hello" (Pure ()))) ==> (Free (Wait (Pure ()))) >>= (\x -> (Free (Render "Hello" (Pure ())))) ==> Free (fmap (>>= (\x -> (Free (Render "Hello" (Pure ()))))) (Wait (Pure ()))) ==> Free (Wait ((>>= (\x -> (Free (Render "Hello" (Pure ()))))) (Pure ()))) ==> Free (Wait ((Pure ()) >>= (\x -> (Free (Render "Hello" (Pure ())))))) ==> Free (Wait (Free (Render "Hello" (Pure ()))))
同じになりましたね。
モナドの枠組みを使えばGameデータ型同士を簡単に合成できることがわかります。
input :: (String -> Free Game ()) -> Free Game () input f = Free (GetInput f) render :: String -> Free Game () render str = Free (Render str (Pure ())) wait :: Free Game () wait = Free (Wait (Pure ())) miniGame = do render "Please input your name." input (\x -> render $ "Your name is " ++ x) wait render "End"
miniGame関数ではdo記法で3種類のGameコンストラクタを合成しています。
あとはFree Game ()をIO ()に変換する、つまり実際の入出力を行う部分を書けば完成です。
runGame :: Free Game () -> IO () runGame (Pure ()) = return () runGame (Free (Wait cont)) = runGame cont runGame (Free (GetInput f)) = getLine >>= \s -> runGame (f s) runGame (Free (Render str cont)) = putStrLn str >> runGame cont main :: IO () main = runGame miniGame
コンパイルしてmain関数を実行すると名前を聞いてくれます。(ソースコードはこの記事の末尾にあります。)
結局Freeモナドを使うとどんな利点があるのでしょうか。
この記事の例ではゲームの構造と入出力が分離できています。
つまり、テストがしやすいということが言えます。
またゲームの構造だけ作っておいてあとで入出力の部分を差し替えたりするのも簡単です。
Freeモナドを使った2Dゲーム用のライブラリというのもあります。
参考
Freeモナドって何なのさっ!? - capriccioso String Creating(Object something){ return My.Expression(something); }
そろそろFreeモナドに関して一言いっとくか - モナドとわたしとコモナド
以下ソースコード
module Main where -- The kind of f is * -> * like Maybe, []. -- The kind of a is * like Int, Char, [Char]. data Free f a = Pure a | Free (f (Free f a)) -- functor is typeclass which "fmap" is defined -- (<$>) :: Functor f => (a->b) -> f a -> f b -- (<$>) = fmap instance Functor f => Functor (Free f) where fmap f (Pure a) = Pure (f a) fmap f (Free fa) = Free (fmap (fmap f) fa) -- applicative is typeclass which derived from functor -- and offer "pure" and <*> -- (<*>) :: f (a -> b) -> f a -> f b instance Functor f => Applicative (Free f) where pure = Pure Pure a <*> Pure b = Pure $ a b Pure a <*> Free mb = Free $ fmap a <$> mb Free ma <*> b = Free $ (<*> b) <$> ma instance Functor f => Monad (Free f) where return = Pure Pure a >>= k = k a Free fm >>= k = Free (fmap (>>= k) fm) -- free1 :: Num a => Free f a free1 :: Free [] Int free1 = Pure 10 free2 :: Free [] Int free2 = Free [Free [Free [Free []]]] free3 :: Free [] Int free3 = Free [Pure 10] data Game cont = GetInput (String -> cont) | Render String cont | Wait cont instance Functor Game where fmap f (GetInput g) = GetInput (f . g) fmap f (Render str cont) = Render str (f cont) fmap f (Wait cont) = Wait (f cont) game1 :: Free Game () game1 = Free (Wait (Free (Render "Hello" (Pure ())))) game1' :: Free Game () game1' = (Free (Wait (Pure ()))) >> (Free (Render "Hello" (Pure ()))) input :: (String -> Free Game ()) -> Free Game () input f = Free (GetInput f) render :: String -> Free Game () render str = Free (Render str (Pure ())) wait :: Free Game () wait = Free (Wait (Pure ())) miniGame = do render "Please input your name." input (\x -> render $ "Your name is " ++ x) wait render "End" runGame :: Free Game () -> IO () runGame (Pure ()) = return () runGame (Free (Wait cont)) = runGame cont runGame (Free (GetInput f)) = getLine >>= \s -> runGame (f s) runGame (Free (Render str cont)) = putStrLn str >> runGame cont main :: IO () main = runGame miniGame
型レベル自然数を使って型付き行列を作ってみた
· 元記事
#動機
研究で行列計算ライブラリを使っているのですが、numpyはなぜか異なる型の行列同士を足せてしまいます。
このせいでデバッグがめちゃくちゃ大変でした。
こんな単純な間違いはコンパイル時に弾いてくれればいいのにと思ったので、違った型の行列は足せないようなちょっとしたライブラリを作ってみました。
※行列を安全に計算したい人のためには
↑のライブラリが既に存在しました。不勉強ですみません。この記事は「型レベル自然数ってこんなに簡単に(僕でも)使えるんだ」、程度に読んでください。
型レベル変数
最近のGHCでは、自然数をパラメーターとするデータ型を定義できる。固定長リストの長さを型に持たせるとか、行列の大きさを型に持たせるとか、そういうことができる
行列の大きさの情報を型に持たせることができる。 →やってみました
仕様
- 行列の足し算・掛け算をサポートする・
- 足し算/掛け算できないものを計算しようとしたらコンパイル時にエラーにする
実装
{-# LANGUAGE DataKinds, KindSignatures #-} {-# LANGUAGE TemplateHaskell #-} module SizeFixedMatrix where import GHC.TypeLits import Data.List.Split import Data.List data SizeFixedMatrix (h :: Nat) (w :: Nat) = MakeMartix [[Integer]] deriving (Eq,Show) mat1 = MakeMartix [[1,2],[2,3]] :: (SizeFixedMatrix 2 2) mat2 = MakeMartix [[2,9],[1,3],[4,3]] :: (SizeFixedMatrix 3 2) mat3 = MakeMartix [[5,9],[3,3],[0,3]] :: (SizeFixedMatrix 3 2) add :: SizeFixedMatrix p q -> SizeFixedMatrix p q -> SizeFixedMatrix p q add (MakeMartix m1) (MakeMartix m2) = MakeMartix (map (\(a,b) -> map (\(c,d) -> c+d) (zip a b)) (zip m1 m2)) mul :: SizeFixedMatrix p q -> SizeFixedMatrix q r -> SizeFixedMatrix p r mul (MakeMartix m1) (MakeMartix m2) = MakeMartix $ splitEvery (length (head m2)) [sum $ zipWith (*) v w | v <- m1, w <- transpose m2]
…これだけです。拍子抜けするくらい簡単に作れました。もう少し凝ったものを作りたかったのですが、締切に間に合いそうにありませんでした。
一応ちょこっとだけ解説しておくと
SizeFixedMatrix p q
が縦p行横q列の行列の型になります。
不適切な型同士でaddしたりmulしたりするとコンパイルエラーになるので安心してプログラミングできます。
結果
$stack ghci (省略) *Main FiniteField Lib SizeFixedMatrix> mat1 MakeMartix [[1,2],[2,3]] *Main FiniteField Lib SizeFixedMatrix> mat2 MakeMartix [[2,9],[1,3],[4,3]] *Main FiniteField Lib SizeFixedMatrix> mat3 MakeMartix [[5,9],[3,3],[0,3]]
足し算
*Main FiniteField Lib SizeFixedMatrix> add mat1 mat2
<interactive>:4:10: error:
• Couldn't match type ‘3’ with ‘2’
Expected type: SizeFixedMatrix 2 2
Actual type: SizeFixedMatrix 3 2
• In the second argument of ‘add’, namely ‘mat2’
In the expression: add mat1 mat2
In an equation for ‘it’: it = add mat1 mat2
mat1は2行2列の行列型、mat2は3行2列の行列型なので足せないと言われてしまいます
*Main FiniteField Lib SizeFixedMatrix> add mat2 mat3 MakeMartix [[7,18],[4,6],[4,6]]
掛け算
*Main FiniteField Lib SizeFixedMatrix> mul mat1 mat2
<interactive>:8:10: error:
• Couldn't match type ‘3’ with ‘2’
Expected type: SizeFixedMatrix 2 2
Actual type: SizeFixedMatrix 3 2
• In the second argument of ‘mul’, namely ‘mat2’
In the expression: mul mat1 mat2
In an equation for ‘it’: it = mul mat1 mat2
*Main FiniteField Lib SizeFixedMatrix>
mat1*mat2(行列積)は存在しません
*Main FiniteField Lib SizeFixedMatrix> mul mat2 mat1 MakeMartix [[20,31],[7,11],[10,17]]
感想
型レベル変数などという難しい名前の機能をはじめて使いました。案外簡単に使えたので、他にも応用できると思います。
参考文献
hstackを教えてもらったツイート
@igrep @a_kawashiro hmatrixのNumeric.LinearAlgebra.Staticがそんな感じです。 https://t.co/e66NSsTNDr
— Masahiro Sakai (@masahiro_sakai) December 15, 2016
CTF入門記
· 元記事
11月の終わりからCTFをかじり始めました。この熱意がいつまでつづくのか分からないのですがとりあえずこの二週間くらいでやったことを書いてみようと思います。
動機
CTFは高校の先輩に一年ほど前から勧められていたのですが放置していました。最近寒いし何か家の中でできることないかなと考えてCTFを思い出したので初めてみました。
CTF入門します
— a_kawashiro (@a_kawashiro) 2015, 11月 27
絶望
別にPCを初めて触るわけでもないし、ksnctfのポイントの低い問題ならなんとかなるやろ…
わからなすぎてペンペン草も生えない
— a_kawashiro (@a_kawashiro) 2015, 11月 30舐めてました。難しすぎる。というわけで梅田の丸善ジュンク堂で

をお買い上げてしまいました。その後も
POSTリクエストの中身をいじって送信するだけのことができない
— a_kawashiro (@a_kawashiro) 2015, 12月 1SQLインジェクションって存在するんだ(無知)
— a_kawashiro (@a_kawashiro) 2015, 12月 4などと(初心者なので)当然すぎる知識不足を露呈し続けました。
感動
FLAG取ったぜ!!
— a_kawashiro (@a_kawashiro) 2015, 11月 30初めて自力で解いたのはksnctf - 8 Basic is secure?だったと思います。嬉しかったですね。(小並感)
n番煎じのyesod入門ーとりあえずプロジェクトを作ってみる
· 元記事
yesodとは
haskellで作られたweb frameworkです。
Yesod Web Framework for Haskell
インストール
公式サイトのYesod quick start guideのとおりに
cabal install yesod-platform yesod-bin
すると依存関係がぶち壊れる可能性大なので
cabal-devを使ってインストールするのがおすすめ。*1
cabal install cabal-dev mkdir -p ~/haskell/yesod cd /haskell/yesod cabal-dev install yesod-platform yesod-bin
起動
cd myFirstYesod/ ../cabal-dev/bin/yesod --dev devel
http://localhost:3000/にアクセスするとテストページが表示されるはずです。
トラブルシューティング1
ここで
cabal: Cannot find the program 'ghc' at 'yesod-ghc-wrapper' or on the path
というエラーメッセージが出たら
cabal-dev/bin
と
cabal-dev/lib
にパスを通しましょう。
トラブルシューティング2
Resolving dependencies... Configuring myFirstYesod-0.0.0... cabal: At least the following dependencies are missing: aeson -any, conduit >=1.0, fast-logger >=2.0, hamlet ==1.1.*, 以下略
こんな感じのエラーが出たら...
"--dev"オプションをつけて起動していますか?
"--dev"オプションをつけないとcabal-devでインストールしたlibraryを読み込みません。*4
*1:最新版のcabalでcabal sandboxを使うのもアリかも。やったことないけど。2013年8月現在のHaskell開発環境 - maoeのブログ
*2:cabal-devについてはここを参考にしました。cabal の使い方 - melpon日記 - HaskellもC++もまともに扱えないへたれのページ
*3:そもそもこのインストール方法はここの丸パクリです。Yes, Yesod! - Just $ A sandbox
*4:yesodのマニュアルが見つからなかったのでこれは未確認情報です。
Haskellでインベーダーゲームを作ってみた
· 元記事
夏休みがあまりに暇なのでインベーダーゲームまがいの物を作ってみました。
ゲーム全体の状態を持つ変数をIORefで包み
一定時間毎に更新することでゲームの状態を変更・保持しています。
(IORefを使うとIOモナドの中で変数が更新できます。(実はよく分かってないのですが…))
基本的な四角形の描画、テクスチャの貼り付けは↓のコピペです。
HaskellとOpenGLを使って四角形を描画しテクスチャを貼り付ける - 春まで冬眠します
スクリーンショット↓

import Graphics.UI.GLUT hiding (Bitmap) import qualified Graphics.Rendering.OpenGL.GL as GL import System.Exit ( exitFailure, exitWith, ExitCode(ExitSuccess) ) import Data.IORef import Codec.Picture.Repa import Data.Array.Repa as R hiding (reshape , map ) import qualified Data.Array.Repa.Repr.ForeignPtr as RF import Foreign.ForeignPtr import Data.Word() import Control.Applicative import Control.Monad import Unsafe.Coerce data Fighter = Fighter { xfighter::GLfloat , yfighter::GLfloat } data Bullet = Bullet { xbullet::GLfloat , ybullet::GLfloat ,lbullet::Int } data Enemy = Enemy { xenemy::GLfloat , yenemy::GLfloat , lenemy::Int } data Flame = Flame { xflame::GLfloat , yflame::GLfloat , lflame::Int } data State = Start | Main deriving (Eq) data Game = Game { fighter::Fighter,bullets::[Bullet],enemies::[Enemy],flames::[Flame],state::State,ftex::TextureObject,btex::TextureObject,etex::TextureObject,fltex::TextureObject,sttex::TextureObject } cz :: GLfloat cz = 50 -- character size timerInterval :: Timeout timerInterval = 40 inite h = map (\i->(Enemy i h 1)) [100,170..500] main :: IO () main = do --GLUTの初期化 initialDisplayMode $= [RGBAMode, DoubleBuffered] initialWindowSize $= Size 640 480 initialize "" [] --ウィンドウを作る createWindow "invader" --テクスチャを読み込む texbullet <- loadTextureFromFile "./bullet.jpg" texfighter <- loadTextureFromFile "./fighter.jpg" texenemy <- loadTextureFromFile "./enemy.jpg" texflame <- loadTextureFromFile "./flame.jpg" texstart <- loadTextureFromFile "./start.jpg" texture Texture2D $= Enabled GL.blend $= GL.Enabled GL.blendFunc $= (GL.SrcAlpha, GL.OneMinusSrcAlpha) game <- newIORef (Game (Fighter 50 50) [(Bullet (1000) 50 0),(Bullet (1000) 50 0),(Bullet (1000) 50 0)] (concat [inite 450,inite 380,inite 310]) [] Start texfighter texbullet texenemy texflame texstart) displayCallback $= display game reshapeCallback $= Just reshape keyboardMouseCallback $= Just (keyboardProc game) addTimerCallback timerInterval $ timerProc (display game) mainLoop processBullet :: [Enemy] -> [Bullet] -> [Bullet] --processBullet es bs = ((map f1) . (map f2) . (map f3)) bs processBullet es bs = map (f1 . f2 . f3) bs where f1 b = b{ybullet=ybullet b+4} f2 b = if (ybullet b)>505 then b{lbullet=0} else b f3 b = if any (\e->g1 (lenemy e)>0&&g1 (xbullet b-xenemy e)<cz/2&&g1 (ybullet b-yenemy e)<cz/2) es then b{lbullet=0} else b g1 f = if f>0 then f else -f processEnemy :: [Bullet] -> [Enemy] -> [Enemy] --processEnemy bs es = ((map f4).(map f1).(map f2)) es processEnemy bs es = map (f4 . f1 . f2) es where f1 e = if lenemy e`div`20`mod`2==0 then e{xenemy=xenemy e-2} else e{xenemy=xenemy e+2} f2 e = if any (\b->(lbullet b)>0&&g1 (xbullet b-xenemy e)<cz/2&&g1 (ybullet b-yenemy e)<cz/2) bs then e{lenemy=0} else e g1 f = if f>0 then f else -f f4 e = if lenemy e>0 then e{yenemy=yenemy e-0.2,lenemy=lenemy e+1} else e processFlame :: [Bullet] -> [Enemy] -> [Flame] -> [Flame] processFlame bs es fs = ((Prelude.++ f4) . (filter f2) . (map f1)) fs where f1 f = f{lflame=lflame f-1} f2 f = (lflame f) > 0 f3 (b,e) = if (lbullet b)>0&&(lenemy e)>0&& g1 (xbullet b-xenemy e)<cz/2&&g1 (ybullet b-yenemy e)<cz/2 then [Flame {xflame=xenemy e,yflame=yenemy e,lflame=10}] else [] f4 = concat (map f3 g2) g1 f = if f>0 then f else -f g2 = concat (map (\b-> (map (\e->(b,e)) es)) bs) processGame :: Game -> Game processGame g = if state g == Main then g{bullets=processBullet (enemies g) (bullets g), enemies=processEnemy (bullets g) (enemies g), flames=processFlame (bullets g) (enemies g) (flames g)} else g display :: IORef Game -> IO () display game = do g <-readIORef game modifyIORef game processGame clearColor $= Color4 0.0 0.0 0.0 0.0 clear [ColorBuffer] loadIdentity renderGame g swapBuffers renderGame :: Game -> IO() renderGame g = if state g == Main then do mapM_ (renderBullet (btex g)) (bullets g) mapM_ (renderEnemy (etex g)) (enemies g) mapM_ (renderFlame (fltex g)) (flames g) render (xfighter (fighter g)) (yfighter (fighter g)) (ftex g) else do currentColor $= Color4 1 1 1 1 textureBinding Texture2D $= Just (sttex g) preservingMatrix $ do -- translate (Vector3 0 0 (0::Double)) renderPrimitive Quads $ do texCoord2f (TexCoord2 0 0) vertex3f (Vertex3 0 0 0.0) texCoord2f (TexCoord2 0 1) vertex3f (Vertex3 0 480 0.0) texCoord2f (TexCoord2 1 1) vertex3f (Vertex3 640 480 0.0) texCoord2f (TexCoord2 1 0) vertex3f (Vertex3 640 0 0.0) renderBullet :: TextureObject -> Bullet -> IO () renderBullet texbullet b = if lbullet b > 0 then render (xbullet b) (ybullet b) texbullet else return () renderEnemy :: TextureObject -> Enemy -> IO () renderEnemy texenemy e = if lenemy e > 0 then render (xenemy e) (yenemy e) texenemy else return () renderFlame :: TextureObject -> Flame -> IO() renderFlame texflame f = if lflame f > 0 then render (xflame f) (yflame f) texflame else return () render :: GLfloat -> GLfloat -> TextureObject -> IO () render x y tex = do currentColor $= Color4 1 1 1 1 textureBinding Texture2D $= Just tex preservingMatrix $ do -- translate (Vector3 0 0 (0::Double)) renderPrimitive Quads $ do texCoord2f (TexCoord2 0 0) vertex3f (Vertex3 (x-cz/2) (y-cz/2) 0.0) texCoord2f (TexCoord2 0 1) vertex3f (Vertex3 (x-cz/2) (y+cz/2) 0.0) texCoord2f (TexCoord2 1 1) vertex3f (Vertex3 (x+cz/2) (y+cz/2) 0.0) texCoord2f (TexCoord2 1 0) vertex3f (Vertex3 (x+cz/2) (y-cz/2) 0.0) texCoord2f = texCoord :: TexCoord2 GLfloat -> IO () vertex3f = vertex :: Vertex3 GLfloat -> IO () --タイマが呼ばれるたびにactを繰り返す timerProc act = do act addTimerCallback timerInterval $ timerProc act --ウィンドウのサイズが変更された時の処理 reshape :: Size -> IO() reshape (Size w h)=do viewport $= (Position 0 0, (Size w h)) --ウィンドウ全体を使う matrixMode $= Projection loadIdentity ortho 0.0 640.0 0.0 480.0 (-1000.0) 1000.0 matrixMode $= Modelview 0 keyboardProc :: IORef Game -> Key -> t -> t1 -> t2 -> IO () keyboardProc game ch st _ _ | ch == SpecialKey KeyLeft = modifyIORef game (\g->g{fighter=changeX (-2) (fighter g)}) | ch == SpecialKey KeyRight = modifyIORef game (\g->g{fighter=changeX 2 (fighter g)}) | ch == Char ' ' = space (readIORef game) | otherwise = return () where changeX dx f = if 0+cz<=xfighter f+dx&&xfighter f+dx<=640-cz then f { xfighter = xfighter f + dx } else f shot = modifyIORef game (\g->g{bullets=shotProcess (fighter g) (bullets g)}) space g' = do g <- g' if state g == Main then shot else modifyIORef game (\g->g{state=Main}) shotProcess :: Fighter -> [Bullet] -> [Bullet] shotProcess f bs = if (all (\b->ybullet b-50>yfighter f) bs) && (any (\b->lbullet b==0) bs) then concat [[(head (filter (\b->lbullet b==0) bs)){xbullet=xfighter f,ybullet=yfighter f,lbullet=1}], (tail (filter (\b->lbullet b==0) bs)), (filter (\b->lbullet b/=0) bs)] else bs -- テクスチャをファイルから読み込む -- 何をやっているのか未だによくわからない -- FreeGameのサンプルをちょっといじったもの loadTextureFromFile :: FilePath -> IO GL.TextureObject loadTextureFromFile path = do content <- delay <$> (flipVertically.imgData) <$> either error id <$> (readImageRGBA path) let (Z :. width :. height :. _) = R.extent content [tex] <- GL.genObjectNames 1 GL.textureBinding GL.Texture2D GL.$= Just tex GL.textureFilter Texture2D $= ((Nearest, Nothing), Nearest) fptr <- liftM RF.toForeignPtr $ R.computeP $ content withForeignPtr fptr $ GL.texImage2D Nothing GL.NoProxy 0 GL.RGBA8 (GL.TextureSize2D (gsizei width) (gsizei height)) 0 . GL.PixelData GL.RGBA GL.UnsignedInt8888 return tex gsizei :: Int -> GL.GLsizei {-# INLINE gsizei #-} gsizei x = unsafeCoerce x
Codeforces Round #176 div 2
· 元記事
久しぶりに(一年ぐらい)Codeforcesに出ました。
正解したのはA問題のみというひどい結果でした。
B問題はコーナーケースで落ち、C問題以降は
手も足も出ませんでした。
もう一度初心に帰って勉強する必要性を感じました。
A. IQ Test
問題概要
4x4の升目が与えられます。各升には"."または"#"が書かれています。
16個の升のどれか一つを書き換えることによって同じ記号が書かれた
2x2の正方形を得ることができるなら"YES"、できないなら"NO"と出力しなさい。
ただし、何も書き換えない状態で条件を満たすなら"YES"と出力しなさい。
解法
16個の升のそれぞれについて書き換えて正方形が得られるかどうかを調べるだけです。
ソースコード
#include <string> #include <vector> #include <algorithm> #include <iostream> using namespace std; bool check(vector<string> &vs){ for(int i=0;i<3;i++) for(int j=0;j<3;j++) if(vs[i][j]==vs[i+1][j]&&vs[i][j]==vs[i][j+1]&&vs[i][j]==vs[i+1][j+1]) return true; return false; } char opposite(char c){ if(c=='#')return '.'; else return '#'; } int main(){ vector<string> vs; for(int i=0;i<4;i++){ string s; cin>>s; vs.push_back(s); } string ans="NO"; if(check(vs)) ans="YES"; for(int i=0;i<4;i++) for(int j=0;j<4;j++){ vs[i][j]=opposite(vs[i][j]); if(check(vs)) ans="YES"; vs[i][j]=opposite(vs[i][j]); } cout<<ans<<endl; return 0; }
B. Pipeline
問題概要
自然数nとkが与えられます。
k以下の互いに異なる自然数l_1,l_2,l_3,...,l_mが
l_1-1+l_2-1+l_3-1+...+l_m-1=n-1
を満たすときmの最小値を求めなさい。
ただし、n=1のときはm=0です。
(これは問題から主要な部分を抽出したものであり
実際の問題文の日本語訳とはかなり異なります。)
解法
l_1-1+l_2-1+l_3-1+...+l_m-1>=n-1
となるmを見つければ良いので(勘)
l_1=k,l_2=k-1,l-3=k-2,...,l-m=k-m+1とします。
このとき
l_1-1+l_2-1+l_3-1+...+l_m-1=(k+k-m+1)*(k-k+m-1+1)/2-m であるので
mの値を二分探索すれば良いです。
ソースコード
#include <iostream> #include <algorithm> using namespace std; typedef long long LL; int main(){ LL n,k; cin>>n>>k; if(n==1){ cout<<0<<endl; return 0; } if(n<=k){ cout<<1<<endl; return 0; } LL u=k-1,d=0; for(int i=0;i<100;i++){ LL m=(u+d)/2; LL a=(k-m)*(k+m-1)/2; if(n-1<=a) d=m; else u=m; } // cout<<"u="<<u<<" d="<<d<<endl; if(n-1<=(k-u)*(k+u-1)/2) cout<<k-u<<endl; else if(n-1<=(k-d)*(k+d-1)/2) cout<<k-d<<endl; else cout<<-1<<endl; return 0; }
Marathon Match 78 の記録
· 元記事
最終的なアルゴリズム
左上(0,0)を出発点として、盤面上に一本の線を引いていく。
1マス進むことを一手として10手探索してはもっとも高い点数が
得られるものを最終的な解に追加していく。このとき輪っかができなくなるものは除外する。
探索と解の追加を繰り返して出発点に戻ってきたら終了する。
経過
1:左上を始点として点数が最大になる輪っかを作るようなビーム探索を書く。
(当時は自分が書いているものがビーム探索であるとは知らなかった。)
※ビーム探索とは幅優先探索において評価の高い解のみを残すような探索のこと。
2:うーん、遅すぎる。
3:だんだん保持する解の個数を減らす。
4:あーもうこれは保持する解を一つにしてもいいんじゃない。
5:保持する解を一つにする。
6:10手づつ深さ優先探索して暫定解に付け足していって輪っかができたら終了するアルゴリズムに変更する。
結果
105人中57位。
初めてのマラソンマッチにしては健闘したと見るか、
修行が足りないと見るかは判断が分かれるところ。(僕の頭の中では)
反省点
大量のテストを簡単に実行できる環境を早めに構築するべきだった。
(最終的にはコマンド一つで100個のテストケースを試すようにした。)
更に、テストから得られた結果を簡単に判断できるようにするべきだった。
良かった点
公式のビジュアライザを改造して、サイズやfillRatioをコマンド引数で変更できるようにし、
更に正しくない(輪っかになっていない)解も目で見れるようにしたのは良かった。
最終提出コード
#include <iostream> #include <cstdio> #include <cstdlib> #include <cmath> #include <string> #include <vector> #include <queue> #include <map> #include <set> #include <algorithm> #include <functional> #include <cstring> #include <sstream> #define FOR(i,k,n) for (int i=(k); i<(int)(n); ++i) #define REP(i,n) FOR(i,0,n) #define FORIT(i,c) for(__typeof((c).begin())i=(c).begin();i!=(c).end();++i) #define SZ(i) ((int)i.size()) #define pb push_back #define mp make_pair #define ALL(X) (X).begin(),(X).end() typedef long long LL; // #define DFS_DEPTH 11 #define MAX_ANS_LENGTH 20200 #define INF 100000000 using namespace std; int dfsDepth=10; int mv[4][2]={{-1,0},{1,0},{0,-1},{0,1}}; int c2m(char c){if(c=='U')return 0;if(c=='D')return 1;if(c=='L')return 2;if(c=='R')return 3;return INF;} int iabs(int n){if(0<=n)return n;else return -n;} vector<string> dia; int H,W; int isVisited[110][110]; int nVisit[110][110]; int nTouch[110][110]; int bfs[110][110]; string udlr="UDLR"; string snake; int point; pair<pair<int,int>,string> ans; int ystart,xstart,yhead,xhead,ygoal,xgoal; class BIT{ public: int bit[MAX_ANS_LENGTH+10]; int sum(int i){ i++; int s=0; while(0<i){ s+=bit[i]; i-=i&-i; } return s; } void add(int i,int x){ i++; while(i<=MAX_ANS_LENGTH+5){ bit[i]+=x; i+=i&-i; } } }; BIT turnCount; int rorlturn(char s,char t){//left turn = 1 right turn = -1 if(s=='U'){if(t=='L')return 1;if(t=='R')return -1;} if(s=='L'){if(t=='D')return 1;if(t=='U')return -1;} if(s=='D'){if(t=='R')return 1;if(t=='L')return -1;} if(s=='R'){if(t=='U')return 1;if(t=='D')return -1;} return 0; } bool isInLoop(const int w,const int v,const char n,const int y,const int x,const char m,const int nv){ if(y==0||y==H||x==0||x==W)return false; //右回りのループを検出 int yy=y,xx=x; if(m=='U')xx--; if(m=='D')xx++; if(m=='L')yy++; if(m=='R')yy--; if(0<isVisited[yy][xx]&&turnCount.sum(nv)-turnCount.sum(nVisit[yy][xx]+1)<=-3) return true; //左回りのループを検出 yy=y,xx=x; if(m=='U')xx++; if(m=='D')xx--; if(m=='L')yy--; if(m=='R')yy++; if(0<isVisited[yy][xx]&&turnCount.sum(nv)-turnCount.sum(nVisit[yy][xx]+1)>=3) return true; int yf=w+mv[c2m(n)][0],xf=v+mv[c2m(n)][1]; if(yf==0||yf==H||xf==0||xf==W)return false; //右回りのループを検出 yy=yf,xx=xf; if(n=='U')xx--; if(n=='D')xx++; if(n=='L')yy++; if(n=='R')yy--; if(0<isVisited[yy][xx]&&turnCount.sum(nv-1)-turnCount.sum(nVisit[yy][xx]+1)<=-3&&rorlturn(n,m)==-1) return true; //左回りのループを検出 yy=yf,xx=xf; if(n=='U')xx++; if(n=='D')xx--; if(n=='L')yy--; if(n=='R')yy++; if(0<isVisited[yy][xx]&&turnCount.sum(nv-1)-turnCount.sum(nVisit[yy][xx]+1)>=3&&rorlturn(n,m)==1) return true; return false; } pair<pair<int,int>,string> DFS(const int y,const int x,const string s,const int p,const char l){ if(dfsDepth==SZ(s)){ return make_pair(make_pair(p,iabs(ygoal-y)+iabs(xgoal-x)),s); }else{ vector<pair<pair<int,int>,string> > res; for(int i=0;i<4;i++){ int yy=y+mv[i][0]; int xx=x+mv[i][1]; if(yy<0||H<yy||xx<0||W<xx) continue; if(ygoal==H&&xgoal==W&&yy<xx) continue; if(ygoal==H&&xgoal==W&&yy==xx&&i==0) continue; if(ygoal==H&&xgoal==W&&y==x+1&&i==2) continue; vector<int> tpos; string ss=s+udlr[i]; char ll=udlr[i]; if(i==0){ if(0<x) tpos.push_back((y-1)*1000+x-1); if(x<W) tpos.push_back((y-1)*1000+x); }else if(i==1){ if(0<x) tpos.push_back(y*1000+x-1); if(x<W) tpos.push_back(y*1000+x); }else if(i==2){ if(0<y) tpos.push_back((y-1)*1000+x-1); if(y<H) tpos.push_back(y*1000+x-1); }else{ if(0<y) tpos.push_back((y-1)*1000+x); if(y<H) tpos.push_back(y*1000+x); } int pp=p; for(int j=0;j<SZ(tpos);j++){ int yyy=tpos[j]/1000,xxx=tpos[j]%1000; if(nTouch[yyy][xxx]==dia[yyy][xxx]-'0') pp--; if(nTouch[yyy][xxx]+1==dia[yyy][xxx]-'0') pp++; nTouch[yyy][xxx]++; } isVisited[yy][xx]++; int oldnVisit=nVisit[yy][xx]; nVisit[yy][xx]=SZ(snake)+SZ(ss); turnCount.add(SZ(snake)+SZ(ss),rorlturn(l,ll)); if((x==0&&i==0)||(y==H&&i==2)||(x==W&&i==1)||(y==0&&i==3)){ ; }else if(ygoal==yy&&xgoal==xx){ if(2<snake.size()+ss.size()&&ans.first.first<point+pp) ans=make_pair(make_pair(point+pp,iabs(ygoal-yy)+iabs(xgoal-xx)),snake+ss); }else if(1<isVisited[yy][xx]){ ; }else{ if(!isInLoop(y,x,l,yy,xx,udlr[i],SZ(snake)+SZ(ss))){ res.push_back(DFS(yy,xx,ss,pp,ll)); } } for(int j=0;j<SZ(tpos);j++){ int yyy=tpos[j]/1000,xxx=tpos[j]%1000; nTouch[yyy][xxx]--; } isVisited[yy][xx]--; nVisit[yy][xx]=oldnVisit; turnCount.add(SZ(snake)+SZ(ss),-1*rorlturn(l,ll)); } if(SZ(res)==0) return make_pair(make_pair(-INF,iabs(ygoal-y)+iabs(xgoal-x)),""); else{ sort(res.begin(),res.end()); return res.back(); } } } string solve(void){ memset(turnCount.bit,0,sizeof(turnCount)); turnCount.add(100,1); if(H<=20){ dfsDepth=12; }else if(H<=30){ dfsDepth=11; }else if(H<=40){ dfsDepth=10; }else if(H<=50){ dfsDepth=10; }else if(H<=60){ dfsDepth=9; }else if(H<=70){ dfsDepth=9; }else if(H<=80){ dfsDepth=9; }else if(H<=90){ dfsDepth=8; }else if(H<=100){ dfsDepth=8; } point=0; FOR(i,0,H)FOR(j,0,W)if(dia[i][j]=='0')point++; memset(isVisited,0,sizeof(isVisited)); memset(nVisit,-1,sizeof(nVisit)); memset(nTouch,0,sizeof(nTouch)); ystart=0;xstart=0;isVisited[0][0]=1;nVisit[0][0]=0; yhead=1;xhead=0;isVisited[1][0]=1;nVisit[1][0]=1; ygoal=H;xgoal=W; snake="D"; ans=make_pair(make_pair(-INF,iabs(ygoal-yhead)+iabs(xgoal-xhead)),""); nTouch[0][0]=1; if(dia[0][0]=='0')point--; if(dia[0][0]=='1')point++; for(int time=0;time<MAX_ANS_LENGTH/dfsDepth/2;time++){ pair<pair<int,int>,string> res=DFS(yhead,xhead,"",0,snake[SZ(snake)-1]); if(res.first.first==-INF)break; point+=res.first.first; snake+=res.second; FOR(i,0,SZ(res.second)){ char ch=res.second[i]; if(ch=='U')yhead--; if(ch=='D')yhead++; if(ch=='L')xhead--; if(ch=='R')xhead++; isVisited[yhead][xhead]++; nVisit[yhead][xhead]=SZ(snake)-SZ(res.second)+i+1; turnCount.add(SZ(snake)-SZ(res.second)+i,rorlturn(snake[SZ(snake)-SZ(res.second)+i-1],snake[SZ(snake)-SZ(res.second)+i])); } } fprintf(stderr,"ans.first.first=%d ans.second=\"%s\"\n",ans.first.first,ans.second.c_str()); if(ans.first.first==-INF){ stringstream tss; tss<<0<<" "<<0<<" "<<snake<<endl; return tss.str(); } pair<pair<int,int>,string> fans=ans; memset(isVisited,0,sizeof(isVisited)); memset(nTouch,0,sizeof(nTouch)); memset(nVisit,-1,sizeof(nVisit)); memset(turnCount.bit,0,sizeof(turnCount.bit)); yhead=0,xhead=0; isVisited[yhead][xhead]++; nVisit[yhead][xhead]=0; FOR(i,0,SZ(fans.second)){ char ch=fans.second[i]; if(ch=='U')yhead--; if(ch=='D')yhead++; if(ch=='L')xhead--; if(ch=='R')xhead++; isVisited[yhead][xhead]++; nVisit[yhead][xhead]=i+1; if(0<i) turnCount.add(i,rorlturn(fans.second[i-1],fans.second[i])); } point=0; for(int y=0;y<H;y++) for(int x=0;x<W;x++){ nTouch[y][x]+=(isVisited[y][x]&&isVisited[y+1][x]); nTouch[y][x]+=(isVisited[y+1][x]&&isVisited[y+1][x+1]); nTouch[y][x]+=(isVisited[y+1][x+1]&&isVisited[y][x+1]); nTouch[y][x]+=(isVisited[y][x+1]&&isVisited[y+1][x]); point+=(nTouch[y][x]==dia[y][x]-'0'); } snake=fans.second+"U"; ystart=H;xstart=W;isVisited[H][W]=1; turnCount.add(SZ(snake)-1,rorlturn('R','U')); yhead=H-1;xhead=W;isVisited[H-1][W]=1;nVisit[H-1][W]=snake.size(); ygoal=0;xgoal=0; ans=make_pair(make_pair(-INF,iabs(ygoal-yhead)+iabs(xgoal-xhead)),""); for(int time=0;time<MAX_ANS_LENGTH/dfsDepth/2;time++){ pair<pair<int,int>,string> res=DFS(yhead,xhead,"",0,snake[SZ(snake)-1]); if(res.first.first==-INF)break; point+=res.first.first; snake+=res.second; FOR(i,0,SZ(res.second)){ char ch=res.second[i]; if(ch=='U')yhead--; if(ch=='D')yhead++; if(ch=='L')xhead--; if(ch=='R')xhead++; isVisited[yhead][xhead]++; nVisit[yhead][xhead]=SZ(snake)-SZ(res.second)+i+1; turnCount.add(SZ(snake)-SZ(res.second)+i,rorlturn(snake[SZ(snake)-SZ(res.second)+i-1],snake[SZ(snake)-SZ(res.second)+i])); } } fprintf(stderr,"ans.first.first=%d ans.second=\"%s\"\n",ans.first.first,ans.second.c_str()); fprintf(stderr,"snake=\"%s\"\n",snake.c_str()); if(ans.first.first==-INF){ stringstream tss; tss<<0<<" "<<0<<" "<<snake<<endl; return tss.str(); } stringstream ss; ss<<0<<" "<<0<<" "<<ans.second; return ss.str(); } class FixTheFence{ public: string findLoop(const vector<string>& __diagram){ dia=__diagram; H=SZ(dia); W=SZ(dia[0]); return solve(); } };
HaskellとOpenGLを使って四角形を描画しテクスチャを貼り付ける
· 元記事
こんな感じになります↓

結構苦労しました。誰かの役に立ってくれると嬉しいです。
参考サイト
いろいろなサンプルがある:Index of /GLUT/examples/RedBook
今回役に立ったのは Tex〜.hs
視野の設定:Lichu's_Base
テクスチャの読み込み・設定:https://github.com/fumieval/free-game/blob/master/Graphics/FreeGame/Backends/GLFW.hs
import Graphics.UI.GLUT hiding (Bitmap) import qualified Graphics.Rendering.OpenGL.GL as GL import System.Exit ( exitFailure, exitWith, ExitCode(ExitSuccess) ) import Data.IORef import Codec.Picture.Repa import Data.Array.Repa as R hiding (reshape) import qualified Data.Array.Repa.Repr.ForeignPtr as RF import Foreign.ForeignPtr import Data.Word import Control.Applicative import Control.Monad import Unsafe.Coerce -- テクスチャをファイルから読み込む -- 何をやっているのか未だによくわからない -- FreeGameのサンプルをちょっといじったもの loadTextureFromFile :: FilePath -> IO GL.TextureObject loadTextureFromFile path = do content <- delay <$> (flipVertically.imgData) <$> either error id <$> (readImageRGBA path) let (Z :. width :. height :. _) = R.extent content [tex] <- GL.genObjectNames 1 GL.textureBinding GL.Texture2D GL.$= Just tex GL.textureFilter Texture2D $= ((Nearest, Nothing), Nearest) fptr <- liftM RF.toForeignPtr $ R.computeP $ content withForeignPtr fptr $ GL.texImage2D Nothing GL.NoProxy 0 GL.RGBA8 (GL.TextureSize2D (gsizei width) (gsizei height)) 0 . GL.PixelData GL.RGBA GL.UnsignedInt8888 return tex gsizei :: Int -> GL.GLsizei {-# INLINE gsizei #-} gsizei x = unsafeCoerce x --タイマの間隔 timerInterval = 40 main = do --回転の角度を初期化 rot <- newIORef 0.0 arg <- newIORef 10.4 --GLUTの初期化 initialDisplayMode $= [RGBAMode, DoubleBuffered] initialWindowSize $= Size 640 480 initialize "" [] --ウィンドウを作る createWindow "DrawPictureWithOpneGL" --テクスチャを読み込む tex <- loadTextureFromFile "読み込みたい画像ファイルへのパス" texture Texture2D $= Enabled GL.blend $= GL.Enabled GL.blendFunc $= (GL.SrcAlpha, GL.OneMinusSrcAlpha) --表示に使うコールバック関数の指定 displayCallback $= display rot arg tex --ウィンドウのサイズが変更された時に呼ぶコールバック関数の指定 reshapeCallback $= Just reshape --キーボードやマウスのコールバック keyboardMouseCallback $= Just (keyboardProc arg) --タイマを作る addTimerCallback timerInterval $ timerProc (display rot arg tex) --GLUTのメインループに入る mainLoop display rot arg texname = do --回転させる w<-readIORef arg --w <- readIORef hoge modifyIORef rot (+w) r <- readIORef rot --背景を黒にする clearColor $= Color4 0.0 0.0 0.0 0.0 clear [ColorBuffer] --単位行列を読み込む loadIdentity -- texCoord,vetexは多くの型について定義されているので -- オーバーロードに関する問題を避けるために以下の関数が必要 let texCoord2f = texCoord :: TexCoord2 GLfloat -> IO () vertex3f = vertex :: Vertex3 GLfloat -> IO () -- 表示 -- 描写する直前に色を指定するようだ -- Color4 赤 緑 青 透明度(0で完全に透明、1で完全に不透明) currentColor $= Color4 1 1 1 1 -- テクスチャの設定 textureBinding Texture2D $= Just texname preservingMatrix $ do translate (Vector3 100 100 (0::Double)) -- rotate 度数法での角度 rotate r (Vector3 0.0 0.0 1.0 :: Vector3 GLdouble) -- 描画 renderPrimitive Quads $ do -- ここから texCoord2f (TexCoord2 0 0) vertex3f (Vertex3 (-50) (-50) 0.0) -- ここまでで一つの頂点の設定(テクスチャに関するもの、場所に関するもの) texCoord2f (TexCoord2 0 1) vertex3f (Vertex3 (-50) 50 0.0) texCoord2f (TexCoord2 1 1) vertex3f (Vertex3 50 50 0.0) texCoord2f (TexCoord2 1 0) vertex3f (Vertex3 50 (-50) 0.0) --バッファの入れ替え swapBuffers --タイマが呼ばれるたびにactを繰り返す timerProc act = do act addTimerCallback timerInterval $ timerProc act --ウィンドウのサイズが変更された時の処理 reshape (Size w h)=do viewport $= (Position 0 0, (Size w h)) --ウィンドウ全体を使う --ビューボリュームの設定- matrixMode $= Projection loadIdentity -- 視野のセッティング ortho 0.0 640.0 0.0 480.0 (-1000.0) 1000.0 -- これ大事(理由はよくわからない) matrixMode $= Modelview 0 --キー入力の処理 keyboardProc arg ch state _ _ -- Qが押されたら終了 | ch == Char 'q' = exitWith ExitSuccess -- Aが押されたら回転速度を二倍にする | ch == Char 'a' = modifyIORef arg (*(2)) -- Sが押されたら回転速度を二分の1にする | ch == Char 's' = modifyIORef arg (*(0.5)) -- それ以外なら回転の方向を変える | state == Down = modifyIORef arg (*(-1)) | otherwise = return ()
HaskellとOpenGLをつかって四角形を描画する
· 元記事
うまく動いたら雛形にでも使ってやってください。
参考サイト
GLUTによる「手抜き」OpenGL入門
プログラミング/Haskell/HSDL - Flightless wing
import Graphics.UI.GLUT import Graphics.Rendering.OpenGL.GLU import System.Exit import Data.IORef --タイマの間隔 timerInterval = 40 main = do --回転の角度を初期化 rot <- newIORef 0.0 arg <- newIORef 10.4 --GLUTの初期化 initialDisplayMode $= [RGBAMode, DoubleBuffered] initialWindowSize $= Size 640 480 initialize "" [] --ウィンドウを作る createWindow "GLUTpractice" --表示に使うコールバック関数の指定 displayCallback $= display rot arg --ウィンドウのサイズが変更された時に呼ぶコールバック関数の指定 reshapeCallback $= Just reshape --キーボードやマウスのコールバック keyboardMouseCallback $= Just (keyboardProc arg) --タイマを作る addTimerCallback timerInterval $ timerProc (display rot arg) --GLUTのメインループに入る mainLoop display rot arg= do --回転させる w<-readIORef arg --w <- readIORef hoge modifyIORef rot (+w) r <- readIORef rot --背景を黒にする clearColor $= Color4 0.0 0.0 0.0 0.0 clear [ColorBuffer] --単位行列を読み込む loadIdentity --表示 currentColor $= Color4 0 0 1 0 -- 描写する直前に色を指定するようだ preservingMatrix $ do translate (Vector3 100 100 (0::Double)) rotate r (Vector3 0.0 0.0 1.0 :: Vector3 GLdouble) -- rotate 度数法での角度 {-- renderPrimitive Quads $ mapM_ vertex [ Vertex3 0.10 0.10 0.0, Vertex3 (-0.10) 0.10 0.0, Vertex3 (-0.10) (-0.10) 0.0, Vertex3 0.10 (-0.10) 0.0 :: Vertex3 GLfloat] --} renderPrimitive Quads $ mapM_ vertex [ Vertex3 50 50 0.0, Vertex3 (-50) 50 0.0, Vertex3 (-50) (-50) 0.0, Vertex3 50 (-50) 0.0 :: Vertex3 GLfloat] --バッファの入れ替え swapBuffers --タイマが呼ばれるたびにactを繰り返す timerProc act = do act addTimerCallback timerInterval $ timerProc act --ウィンドウのサイズが変更された時の処理 reshape (Size w h)=do viewport $= (Position 0 0, (Size w h)) --ウィンドウ全体を使う --ビューボリュームの設定- matrixMode $= Projection loadIdentity ortho 0.0 640.0 0.0 480.0 (-1000.0) 1000.0 -- ortho2D 0.0 640.0 0.0 480.0 matrixMode $= Modelview 0 -- これ大事 {- --少し後ろから撮影 lookAt (Vertex3 0.0 0.0 (600.0)) (Vertex3 0.0 0.0 0.0) (Vector3 0.0 1.0 0.0) matrixMode $= Modelview 0 -} --キー入力の処理 keyboardProc arg ch state _ _ | ch == Char 'q' = exitWith ExitSuccess --qが押されたら終了 | ch == Char 'a' = modifyIORef arg (*(2)) | ch == Char 's' = modifyIORef arg (*(0.5)) | state == Down = modifyIORef arg (*(-1)) --それ以外なら回転の方向を変える | otherwise = return ()
cabal install を使う前に
· 元記事
apt-cache search パッケージ名
とするとよいかも
AOJ 0090 Overlaps of Seals
· 元記事
問題の内容
10*10の正方形上に半径1の円をn個(n<=100)配置する。
最も多くの円の内部に含まれる正方形上の点はいくつの円に含まれるか。
(円の内部とは円周上も含む。)
解法
想定解法では任意の二つの円についてその交点を求め
いくつの円に含まれるかを数えるらしい。
AOJ : 0090 - Overlaps of Seals - Respect2Dの日記
AOJ Problem 0090 : Overlaps of Seals - kyuridenamidaのチラ裏
AOJ 0090: Overlaps of Seals:Snowing day:So-net blog
嘘解法
正方形上の点をできるだけ多く列挙して
その一つ一つについていくつの円に含まれるかを調べる。
円に含まれるかどうかの判定を甘くすると通った。
ソース
#include <stdio.h> #include <string.h> #include <algorithm> #include <vector> #include <iostream> #include <map> #include <set> using namespace std; typedef long long LL; double x[100],y[100]; int main(){ int n; while(1){ scanf("%d",&n); if(n==0) break; for(int i=0;i<n;i++) scanf("%lf,%lf\n",&x[i],&y[i]); int ans=0; for(double a=0;a<=10.1;a+=0.01) for(double b=0;b<=10.1;b+=0.01){ int t=0; for(int i=0;i<n;i++) if((a-x[i])*(a-x[i])+(b-y[i])*(b-y[i])<=1.001) t++; ans=max(ans,t); } printf("%d\n",ans); } return 0; }
cabal install monadius したら結構ハマった
· 元記事
結局こんなことをやった
cabal install OpenGLRaw --reinstall
cabal install StateVar --reinstall
cabal install Tensor --reinstall
cabal install ObjectName --reinstall
cabal install OpenGL --reinstall
cabal install GLURaw --reinstall
cabal install array-0.3.0.1 --reinstall
cabal install containers --reinstall
cabal install GLUT --reinstall
cabal install directory-1.1.0.0 --reinstall
cabal install monadius --reinstall
- history からインストールに必要だと思われるコマンドを抜粋した。
- よく分からないのでとりあえず全部 --reinstall フラグをつけているけど要らないと思う。
ハマりポイント
cabal install array-0.3.0.1 --reinstall
cabal install directory-1.1.0.0 --reinstall
- "cabal install array"だけでバージョン指定をしなかったり、"cabal install directory"だけだと最新版を入れようとしてくれるが、入らない。
JOI2012春合宿
· 元記事
一日目:125
二日目:11
三日目:10
四日目:70
Haskellで文字列圧縮(エンコーダーのみ)
· 元記事
仕組み
- 各文字の出現頻度を数えて一番多いものに"1"、二番目に"01"、三番目に"001"を割り振る。
- ex "aaaaaaaaaaaa" -> "11111111111"
- その後6ビットずつに区切る
- ex "11111111111" -> ["111111","111111"]
- 2進数として読み、文字に変換する
- ex ["111111","111111"] -> "``"
動作例
Main> compress "aaaaaaaaaaaaabbbbbbbbbbbbbb"
Just "KKKK_`0"
Main> compress "qwerrfvghhvjnbknjkfaghjgfjda"
Just "!1!%!%%%B5B\"#!)A!$)#1;C\"!#1"
課題
文字の種類が多いものはうまく圧縮できない。
生成したビット列の長さが6で割りきれないときは勝手に0を補っている。
返り値が Maybe String 。
デコーダがない。(致命的)
ソースコード
import Data.Map as M import Data.List import Data.Char allChar = Prelude.map chr [0..127] genDict str = fromList $ Prelude.map func $ zip [1..] $ Prelude.map snd $ reverse $ sort $ zip (Prelude.map (countChar str) allChar) allChar where func (a,b) = (b,makeCodeLengthN a) makeCodeLengthN 1 = "1" makeCodeLengthN n = '0' : makeCodeLengthN (n-1) countChar [] _ = 0 countChar (x:xs) c = if x==c then 1 + countChar xs c else countChar xs c translate [] d = return [] translate (x:xs) d = do y <- M.lookup x d ys <- translate xs d return (y++ys) bitToChar str = func (reverse str) 0 where func [] a = chr (33+a) func (x:xs) a = if x=='1' then func xs (a*2+1) else func xs (a*2) split6 str = if length str < 6 then [str] else fst (splitAt 6 str) : split6 (snd (splitAt 6 str) ) compress str = do a <- translate str $ genDict str return ( Prelude.map bitToChar (split6 a) )
Haskellでheadコマンド
· 元記事
Haskellで作るものが思いつかないのでheadコマンドを実装してみた。
main = do cs <- getContents putStr $ unlines $ take 10 $ lines cs
std::setでデータ同士の間に順位付けができないと無視されてしまう件
· 元記事
#include <stdio.h> #include <set> using namespace std; struct Data{ int a,b; }; Data makeD(int aa,int bb){ Data d; d.a=aa,d.b=bb; return d; } bool operator < (const Data &d,const Data &e){ return d.a<e.a; } int main(){ set<Data> se; se.insert(makeD(0,1)); se.insert(makeD(0,2)); printf("se.size()==%d\n",se.size()); for(set<Data>::iterator it=se.begin();it!=se.end();it++) printf("a=%d b=%d\n",(*it).a,(*it).b); return 0; }
を実行すると
se.size()==1
a=0 b=1
となってしまう。
これはsetが(0,1)と(0,2)の違いを分からないからである。
se.size()==2
a=0 b=1
a=0 b=2
このようにするには
bool operator < (const Data &d,const Data &e){ if(d.a==e.a) //<= return d.b<e.b; //<= return d.a<e.a; }
こんな風に書き換えると良い。
HaskellでBrainfuckの処理系を実装する
· 元記事
課題
'.' ポインタが指す値を出力に書き出す。C言語の「putchar(*ptr);」に相当。
IOモナドを使いたくない(使えない)のでマシン状態の中にディスプレイを作って処理した。
',' 入力から1バイト読み込んで、ポインタが指す先に代入する。C言語の「*ptr=getchar();」に相当。
複雑になりそうだったので実装しなかった。
-- Brainfuck.hs import Data.Char main = do cs <- getContents putStrLn $ runBrainfuck cs runBrainfuck p = display $ runOneStep $ Machine p 0 [0,0..] 0 "" runOneStep (Machine a b c d e) = if (length a) == b then Machine a b c d e else runOneStep $ incrementProgramPointer func where func = case (a!!b) of '>' -> incrementPointer (Machine a b c d e) '<' -> decrementPointer (Machine a b c d e) '+' -> incrementMemory (Machine a b c d e) '-' -> decrementMemory (Machine a b c d e) '.' -> putCharToDisplay (Machine a b c d e) ']' -> jumpEnd (Machine a b c d e) '[' -> jumpStart (Machine a b c d e) otherwise -> (Machine a b c d e) incrementProgramPointer (Machine a b c d e) = Machine a (b+1) c d e data Machine = Machine { program::String,programPointer::Int,memory::[Int],memoryPointer::Int,display::String } deriving Show incrementPointer (Machine a b c d e) = Machine a b c (d+1) e decrementPointer (Machine a b c d e) = Machine a b c (d-1) e putCharToDisplay (Machine a b c d e) = Machine a b c d (e++[chr (c!!d)]) incrementMemory (Machine a b c d e) = Machine a b (changeMemory c 0 d (+ 1))d e decrementMemory (Machine a b c d e) = Machine a b (changeMemory c 0 d (subtract 1))d e jumpStart (Machine a b c d e) = if (c!!d)==0 then Machine a (func 0 b) c d e else Machine a b c d e where func depth pos = case (a!!pos) of '[' -> func (depth+1) (pos+1) ']' -> if depth==1 then pos else func (depth-1) (pos+1) otherwise -> func depth (pos+1) jumpEnd (Machine a b c d e) = if(c!!d)==0 then Machine a b c d e else Machine a (func 0 b) c d e where func depth pos = case (a!!pos) of '[' -> if depth==1 then pos else func (depth-1) (pos-1) ']' -> func (depth+1) (pos-1) otherwise -> func depth (pos-1) changeMemory [] i k f = [] changeMemory (x:mem) i k f = if i==k then f x:mem else x:changeMemory mem (i+1) k f
情報オリンピックの過去問の入出力をチェックするのに便利だったコマンド
· 元記事
nkf -Lu -overwrite TEST.txt
とやるとファイル中のCRLFが全てLFになって便利だった。
JOI2007 春合宿 1日目 2番
· 元記事
Factorial: 階乗
· 元記事
解法
- nを素因数分解して
- p1^e1*p2^e2*p3^e3*......pr^erとなったとき、 m1!%p1^e1==0,m2!%p2^e2,m3!%p3^e3,.....となる
- 最小のm1,m2,m3...を求めて
- m1,m2,m3....の中で最も大きな値が答え
#include <stdio.h> #include <vector> #include <map> using namespace std; typedef long long LL; vector<pair<LL,int> > primeFactor(LL num){ //first->prime,second->pow{ if(num==1){ vector<pair<LL,int> > r; r.push_back(make_pair(1,1)); return r; } else{ vector<pair<LL,int> > r; for(LL i=2;i*i<=num;i++){ if(num%i==0){ int p=0; while(num%i==0) num/=i,p++; r.push_back(make_pair(i,p)); } } if(num!=1) r.push_back(make_pair(num,1)); return r; } } int main(){ LL n; scanf("%lld",&n); vector<pair<LL,int> > ps=primeFactor(n); LL res=0; for(int i=0;i<(int)ps.size();i++){ LL a=0,b=ps[i].second; while(0<b){ a+=ps[i].first; for(LL t=ps[i].first;a%t==0;t*=ps[i].first) b--; } res=max(res,a); } printf("%lld\n",res); return 0; }
AOJ 1203
· 元記事
- 問題文要約
記号を含む文字列が与えられるので、記号を除いて小文字を大文字に変換した後その文字列が含む回文を列挙せよ。ただしcXcとなる回文が存在した場合Xを出力してはならない。
- 解法
dp[i][j]=(i文字目からj文字目までが回文となっているかどうか) でDPする。
#include <string> #include <iostream> #include <set> #include <string.h> using namespace std; int dp[1030][1030]; int main(){ string s; while(getline(cin,s)){ string in; for(int i=0;i<(int)s.size();i++) if('A'<=s[i]&&s[i]<='Z') in+=s[i]; else if('a'<=s[i]&&s[i]<='z') in+=s[i]-'a'+'A'; int n=in.size(); memset(dp,0,sizeof(dp)); set<string> ans,ng; for(int i=0;i<n;i++) dp[i][i]=1,dp[i][i+1]=1; for(int l=2;l<=n;l++) for(int i=0;i+l<=n;i++) if(dp[i+1][i+1+l-2]==1&&in[i]==in[i+l-1]){ dp[i+1][i+l-2]=0; ng.insert(in.substr(i+1,l-2)); dp[i][i+l]=1; ans.insert(in.substr(i,l)); } for(set<string>::iterator it=ans.begin();it!=ans.end();it++) if(3<=(*it).size()&&ng.find(*it)==ng.end()) cout<<*it<<" "; cout<<endl; } return 0; }
AOJ 1204
· 元記事
- 解法
最後のnクロックの状態と次にpipelineに入れられる仕事の番号を使って拡張ダイクストラをする。int dist[21][1<<20]とかやるとMLEするのでsetを使う。
//AOJ1204 #include <stdio.h> #include <vector> #include <string> #include <string.h> #include <queue> #include <map> #include <set> #define INF (1<<20) #define W 10 using namespace std; struct State{ int time,next,line; }; State makeS(int t,int nn,int l){ State s; s.time=t,s.next=nn,s.line=l; return s; } bool operator < (const State &s,const State &t){ return s.time>t.time; } int n; int used[5]; char as[5][30]; int pi[30]; int check(int line){ memset(used,0,sizeof(used)); for(int i=n-1;0<=i;i--) if(line&(1<<i)) used[pi[n-1-i]]++; for(int i=0;i<5;i++) if(1<used[i]) return 0; return 1; } int main(){ while(1){ scanf("%d",&n); if(n==0) break; for(int i=0;i<5;i++)scanf("%s",as[i]); for(int i=0;i<n;i++)for(int j=0;j<5;j++)if(as[j][i]=='X')pi[i]=j; int ans; priority_queue<State> que; set<pair<int,int> > se; que.push(makeS(0,0,0)); se.insert(make_pair(0,0)); while(!que.empty()){ State s=que.top();que.pop(); if(s.next==10&&s.line==1){ans=s.time;break;} int l=s.line>>1,ok=check(l); if(!ok) continue; if(s.next==W){ if(se.find(make_pair(W,l))==se.end()){ se.insert(make_pair(W,l)); que.push(makeS(s.time+1,W,l)); } }else{ int ll=l|(1<<(n-1)); if(check(ll)){ if(se.find(make_pair(s.next+1,ll))==se.end()){ se.insert(make_pair(s.next+1,ll)); que.push(makeS(s.time+1,s.next+1,ll)); } } if(se.find(make_pair(s.next,l))==se.end()){ se.insert(make_pair(s.next,l)); que.push(makeS(s.time+1,s.next,l)); } } } printf("%d\n",ans); } }
JOI2012本選5番
· 元記事
#include <vector> #include <queue> #include <stdio.h> #include <algorithm> #include <string.h> #include <queue> #define MAX_V 100000+10 #define MAX_Q 100000+10 #define INF (1<<28) #define ROOT 1 using namespace std; struct Edge{ int to,cost; }; Edge makeE(int t,int c){ Edge e;e.to=t,e.cost=c; return e; } bool operator < (const Edge &e,const Edge &f){ return e.cost>f.cost; } struct Biedge{ int a,b,cost; }; Biedge makeB(int aa,int bb,int c){ Biedge e;e.a=aa,e.b=bb,e.cost=c; return e; } bool operator < (const Biedge &e,const Biedge &f){ return e.cost>f.cost; } class Unionfind{ public: vector<int> data; Unionfind(int n){ for(int i=0;i<n;i++) data.push_back(i); } int root(int a){ //printf("root(%d)\n",a); if(data[a]==a) return a; else return data[a]=root(data[a]); } void set(int a,int b){ if(root(a)==root(b)) return; else data[root(a)]=root(b); } }; int fest[MAX_V],dist[MAX_V],qa[MAX_Q],qb[MAX_Q],qr[MAX_Q],depth[MAX_V],score[32][MAX_V],parent[32][MAX_V]; vector<Biedge> biedge; vector<Edge> G[MAX_V]; vector<Edge> MG[MAX_V]; void dfs(int v,int p,int s,int d){ parent[0][v]=p; score[0][v]=s; depth[v]=d; for(int i=0;i<(int)MG[v].size();i++) if(MG[v][i].to!=p) dfs(MG[v][i].to,v,MG[v][i].cost,d+1); } void init(int n){ dfs(ROOT,-1,dist[ROOT],0); for(int k=0;k+1<32;k++) for(int i=0;i<n;i++){ if(parent[k][i]<0) parent[k+1][i]=-1; else{ score[k+1][i]=min(score[k][parent[k][i]],score[k][i]); parent[k+1][i]=parent[k][parent[k][i]]; } } } int lca(int u,int v){ int res=INF; if(depth[u]>depth[v]) swap(u,v); for(int k=0;k<32;k++) if((depth[v]-depth[u])>>k&1){ res=min(res,score[k][v]); v=parent[k][v]; } //printf("lca-m\n");fflush(stdout); if(u==v) return res; for(int k=31;0<=k;k--) if(parent[k][u]!=parent[k][v]){ res=min(res,score[k][u]); u=parent[k][u]; res=min(res,score[k][v]); v=parent[k][v]; } res=min(res,score[0][u]); res=min(res,score[0][v]); return res; } int main(){ int n,m,k,q; scanf("%d%d%d%d",&n,&m,&k,&q); for(int i=0;i<m;i++){ int a,b,c; scanf("%d%d%d",&a,&b,&c); G[a].push_back(makeE(b,c)); G[b].push_back(makeE(a,c)); biedge.push_back(makeB(a,b,0)); } for(int i=0;i<k;i++) scanf("%d",fest+i); for(int i=0;i<q;i++) scanf("%d%d",qa+i,qb+i); for(int i=0;i<MAX_V;i++) dist[i]=INF; priority_queue<Edge> que; for(int i=0;i<k;i++){ que.push(makeE(fest[i],0)); dist[fest[i]]=0; } while(!que.empty()){ int t=que.top().to,c=que.top().cost; que.pop(); if(dist[t]<c) continue; for(int i=0;i<(int)G[t].size();i++){ int tt=G[t][i].to,cc=c+G[t][i].cost; if(cc<dist[tt]){ que.push(makeE(tt,cc)); dist[tt]=cc; } } } for(int i=0;i<m;i++) biedge[i].cost=min(dist[biedge[i].a],dist[biedge[i].b]); sort(biedge.begin(),biedge.end()); memset(qr,-1,sizeof(qr)); Unionfind un(n+1); for(int i=0;i<m;i++){ if(un.root(biedge[i].a)!=un.root(biedge[i].b)){ un.set(biedge[i].a,biedge[i].b); MG[biedge[i].a].push_back(makeE(biedge[i].b,biedge[i].cost)); MG[biedge[i].b].push_back(makeE(biedge[i].a,biedge[i].cost)); } } init(n+1); for(int i=0;i<q;i++) printf("%d\n",lca(qa[i],qb[i])); return 0; }
JOI2012本選4番
· 元記事
講義資料 - いもす研 (imos laboratory)
↑参照。
累積和を使ったコード
#include <stdio.h> int nails[5010][5010]; int main(){ int n,m; scanf("%d%d",&n,&m); for(int i=0;i<m;i++){ int a,b,x; scanf("%d%d%d",&a,&b,&x); nails[a][b]++,nails[a][b+1]--; nails[a+x+1][b]--,nails[a+x+2][b+1]++; nails[a+x+1][b+x+2]++,nails[a+x+2][b+x+2]--; } for(int i=0;i<5010;i++){ int a=0; for(int j=0;j<5010;j++){ a+=nails[i][j]; nails[i][j]=a; } } for(int i=0;i<5010;i++){ int a=0; for(int j=0;j<5010;j++){ a+=nails[j][i]; nails[j][i]=a; } } for(int i=0;i<5010;i++){ int a=0; for(int j=0;i+j<5010;j++){ a+=nails[i+j][j]; nails[i+j][j]=a; } } int ans=0; for(int i=0;i<5010;i++) for(int j=0;j<5010;j++) if(0<nails[i][j]) ans++; printf("%d\n",ans); return 0; }
最大値の伝搬を使ったコード(ただし後ろから)。
#include <stdio.h> #include <string.h> #include <algorithm> using namespace std; int nails[5010][5010]; int main(){ int n,m; scanf("%d%d",&n,&m); memset(nails,0,sizeof(nails)); for(int i=0;i<m;i++){ int a,b,x; scanf("%d%d%d",&a,&b,&x); nails[a+x][b+x]=max(nails[a+x][b+x],x+1); } int ans=0; for(int y=n;0<=y;y--) for(int x=n;0<=x;x--){ if(0<nails[y][x]) ans++; if(0<x) nails[y][x-1]=max(nails[y][x-1],nails[y][x]-1); if(0<x&&0<y) nails[y-1][x-1]=max(nails[y-1][x-1],nails[y][x]-1); } printf("%d\n",ans); return 0; }
JOI2012本選3番
· 元記事
時間がSにかぶるときだけ気をつけてdpすればOK。
#include <stdio.h> #include <string.h> #include <algorithm> using namespace std; int n,S,T; int A[3010],B[3010]; int dp[3010][3010]; int rec(int store,int time){ if(store==n) return 0; else if(dp[store][time]!=-1) return dp[store][time]; else{ int res=rec(store+1,time); if(time<S&&S<time+B[store]){ if(S+B[store]<=T){ res=max(res,rec(store+1,S+B[store])+A[store]); } }else{ if(time+B[store]<=T){ res=max(res,rec(store+1,time+B[store])+A[store]); } } return dp[store][time]=res; } } int main(){ scanf("%d%d%d",&n,&T,&S); for(int i=0;i<n;i++) scanf("%d%d",A+i,B+i); memset(dp,-1,sizeof(dp)); int ans=rec(0,0); printf("%d\n",ans); return 0; }
JOI2012本選2番
· 元記事
dp[ap=Aの何枚目か][bp=Bの何枚目か]でDPした。
#include <stdio.h> #include <string.h> #include <algorithm> using namespace std; int dp[5010][5010]; int ac[5010],bc[5010]; int rec(int ap,int bp){ if(ap<0||bp<0) return 0; else if(dp[ap][bp]!=-1) return dp[ap][bp]; else{ int r=rec(ap-1,bp); if(ac[ap]==bc[bp]) r=max(r,rec(ap-1,bp-1)+1); return dp[ap][bp]=r; } } int main(){ int an,bn; scanf("%d%d",&an,&bn); for(int i=0;i<an;i++) scanf("%d",ac+i); for(int i=0;i<bn;i++) scanf("%d",bc+i); memset(dp,-1,sizeof(dp)); int ans=0; for(int i=0;i<an;i++) for(int j=0;j<bn;j++) ans=max(ans,rec(i,j)); printf("%d\n",ans); return 0; }
JOI2012本選1番
· 元記事
解説を聞いてから自分のやっていたことがRunLength圧縮と同じであると気づいた。
#include <stdio.h> #include <vector> using namespace std; char in[10000000+10]; int main(){ scanf("%s",in); vector<int> ch,sq; int c=in[0],s=1; for(int i=1;in[i]!=0&&in[i]!='\n';i++){ if(c!=in[i]){ ch.push_back(c); sq.push_back(s); c=in[i]; s=1; }else{ s++; } } ch.push_back(c); sq.push_back(s); int ans=0; for(int i=0;i+2<(int)ch.size();i++){ if(ch[i]=='J'&&ch[i+1]=='O'&&ch[i+2]=='I'&&sq[i+1]<=min(sq[i],sq[i+2])) ans=max(ans,min(sq[i],sq[i+1])); } printf("%d\n",ans); return 0; }
JOI2012本選まとめ
· 元記事
↑の大会に参加してきました。
実力を限界まで発揮できたと思います。
合宿は問題を楽しむだけにしようと思います。
JOI2011本選5番
· 元記事
kについて二分探索した後、その判定を頑張ってO(n)で出来るようにする。
#include <stdio.h> #include <utility> #include <set> #include <algorithm> #include <vector> #define MAX_V 1000100 using namespace std; int n; int ai[MAX_V],can[MAX_V],bi2ai[MAX_V]; pair<int,int> bi[MAX_V]; int ok(int k) { if(k==0) return 1; double sumA=0; for(int i=0;i<n;i++) can[i]=1; for(int i=0;i<k;i++) sumA+=ai[i]; int lastA=k-1; for(int b=0;b+k<=n;b++) { if(sumA/(double)k<=(double)bi[b].first) return 1; int tP=bi2ai[b]; can[tP]=0; if(tP<=lastA) { sumA-=ai[tP]; lastA++; if(lastA>=n) return 0; while(!can[lastA]) lastA++; sumA+=ai[lastA]; } } return 0; } int main() { scanf("%d",&n); for(int i=0;i<n;i++) { int a,b; scanf("%d%d",&a,&b); ai[i]=a; can[i]=1; bi2ai[i]=-1; bi[i]=make_pair(b,-a); } sort(ai,ai+n); sort(bi,bi+n); for(int i=0;i<n;i++) { int a=-bi[i].second; int tB=lower_bound(ai,ai+n,a)-ai; int tE=upper_bound(ai,ai+n,a)-ai; int tP; for(tP=tB;tP<tE;tP++) if(can[tP]) break; can[tP]=0; bi2ai[i]=tP; } int b=0,c=n; while(1<c-b) { int m=(b+c)/2; if(ok(m)) b=m; else c=m; } if(ok(c)) printf("%d\n",c); else printf("%d\n",b); return 0; }
SuperCon2011に参加しました
· 元記事
2011年8月22日(月) 〜 26日(金)にかけて大阪大学豊中キャンパスで
SuperCon2011にチームMKとして参加してきました。
今回の課題は「https://www.cp.cmc.osaka-u.ac.jp/supercon/naklon.html」。
リンクをクリックして表示されるのはSIZE=8のものですが
実際の本選はSIZE=250という巨大なくろんなので全探索はできません。
それではどうするか。
僕達のチーム(主に僕)は2つの方針を考えました。
1:1000手ほど乱択しそれを何度か繰り替えして最もよいものを選ぶことを何度か繰り返す
2:5手ほど全探索して評価関数で評価して最も良い手を選ぶ。
1は相方に実装してもらったのですがあまり結果が芳しくなかったので
僕は2を頑張っていました。
3日目の昼頃に評価関数を得点÷落ちた玉に変えた所
非常に結果が良かったので最後までその方針で行きました
結果は5位でした。
2位から5位まではほとんど同じ方針だったということなので悔しいです
Haskellで数式をパースする
· 元記事
使い方
:load で読み込んだ後
myEquation "1+2*3"
import Text.ParserCombinators.Parsec myDigit :: Parser Char myDigit = oneOf ['0'..'9'] myNumberString :: Parser String myNumberString = do c<-myDigit do cs <- myNumberString return (c:cs) <|> return [c] myNumber :: Parser Integer myNumber = do s<-myNumberString return (read s) myPlusMinus :: Parser Char myPlusMinus = oneOf "+-" myMultiDiv :: Parser Char myMultiDiv = oneOf "*/" myCalc '+' left right = (left+right) myCalc '-' left right = (left-right) myCalc '*' left right = (left*right) myCalc '/' _ 0 = 0 myCalc '/' left right = (left `div` right) myEquation :: Parser Integer myEquation = do{ left<-myFactor; rest left } where rest l = do{ op<-myPlusMinus ;r<-myFactor ;rest (myCalc op l r) } <|> return l {-<|> do{ op<-myPlusMinus ;right<-myEquation ;return (myCalc op x right }-} myFactor :: Parser Integer --myFactor = do{ x<-myTerm; rest x} myFactor = do {left<-myTerm; rest left } where rest l = do{ op<-myMultiDiv ;r<-myTerm ;rest (myCalc op l r) } <|> return l {- left<-myFactor op<-myMultiDiv right<-myTerm return (myCalc op left right) <|> myTerm-} myTerm :: Parser Integer myTerm = do char '(' x<-myEquation char ')' return x <|> myNumber {- myExpr2List String->[String] myExpr2List "" = "" myExpr2List x:xs = if myPlusMinus myFactor :: Parser Integer myFactor = do char '(' x<-myExpr char ')' return x <|> myExpr myExpr :: Parser Integer myExpr = do left<-myNumber do op<-myPlusMinus right<-myFactor return (myCalc op left right) <|> return left <|> myFactor -}
HaskellでHTMLをパースする
· 元記事
使い方
hoge.hsに下のコードを保存する。
runghc "hoge.hs" "target.html" ※ダブルクオーテーションは必要
このままでは空行が大量に含まれているので
runghc "hoge.hs" "target.html" | sed '/^ *$/d'
すると良い
--sed '/^ *$/d' import Text.ParserCombinators.Parsec import System.Environment data MyTag = MakeMyTag {tagName::String,tagContent::[String]} deriving (Eq,Show) testMyTag = MakeMyTag "aa" ["bb","cc"] data MyHTMLContent = MakeContentChild { myLeftTag::MyTag,myContentMain::[MyHTMLContent],myRightTag::MyTag } | MakeTagSelf MyTag | MakePlainText { myText::String } | MakeComment deriving (Eq,Show) testHTMLContent1 = (MakePlainText "dd") testHTMLContent2 = (MakeTagSelf testMyTag) testHTMLContent3 = (MakeContentChild testMyTag [testHTMLContent1,testHTMLContent2] testMyTag) outputHTML (MakeComment) n = "" outputHTML (MakePlainText "") n = "" outputHTML (MakePlainText s) n = (sTab n++s++"\n") outputHTML (MakeTagSelf x) n = "" outputHTML (MakeContentChild l con r) n = sCon con (n+1) sTab 0 = "" sTab n = " "++sTab (n-1) sCon [] _ = "" sCon (x:xs) n = (outputHTML x n++sCon xs n) main = do inStr<-getArgs tmp <- parseFromFile myHTML (head inStr) case tmp of Left err -> do {print "error"; } Right xs -> do {putStrLn (func xs); } func :: [MyHTMLContent]->String func [] = "" func (x:xs) = (outputHTML x 0)++(func xs) isNotEqual c = if c=='=' then False else True myDigit :: Parser Char myDigit = oneOf ['0'..'9'] myNumberString :: Parser String myNumberString = do c<-myDigit do cs <- myNumberString return (c:cs) <|> return [c] myTagContentLeft :: Parser String myTagContentLeft = many1 (noneOf "=>/") -- myTagContentLeft = many1 (satisfy (\c -> c /= '=')) --myTagContentLeft = many1 (alphaNum <|> char ':') {-where rest = do{ x<-satisfy (\c -> c /= '=') ; xs<-rest ; return (x:xs) } <|> return "" -} myTagContentRight :: Parser String myTagContentRight = do lD<-char '"' mD<-inC rD<-char '"' return ([lD]++mD++[rD]) <|> myNumberString where inC = do{ x<-satisfy (\c -> c /= '"') ; xs<-inC ; return (x:xs) } <|> return "" myTagContent :: Parser String myTagContent = do left<-myTagContentLeft char '=' right<-myTagContentRight return (left++"="++right) myTagName :: Parser String myTagName = many1 alphaNum myEndTag :: Parser MyTag myEndTag = do string "</" many space name<-myTagName many space char '>' return (MakeMyTag name []) myBeginTag :: Parser MyTag myBeginTag = do char '<' name<-myTagName many space tagContent<-many p char '>' return (MakeMyTag name tagContent) where p = do r<-myTagContent many space return r mySelfTag :: Parser MyTag mySelfTag = do char '<' name<-myTagName many space tagContent<-many p string "/>" return (MakeMyTag name tagContent) where p = do r<-myTagContent many space return r myComment :: Parser MyHTMLContent myComment = do string "<!--" rest "aa" char '>' return MakeComment -- <?> "myComment" where rest before = if before == "--" then return () else do x<-anyChar rest (tail before++[x]) myContent :: Parser MyHTMLContent myContent = do {p<-many1 (satisfy (\c-> c/='<')); return ( MakePlainText p)} <|> myBeginAndEndTag <|> do {t<-try mySelfTag; return (MakeTagSelf t)} <|> try myComment <?> "myContent" {--myContent = try( do {b<-myBeginTag; l<-myContentList; e<-myEndTag; return (MakeContentChild b l e)}) <|> try( do {t<-mySelfTag; return (MakeTagSelf t)}) <|> do {p<-many1 (satisfy (\c-> c/='<')); return ( MakePlainText p)}--} myBeginAndEndTag :: Parser MyHTMLContent myBeginAndEndTag = do b<-try myBeginTag l<-myContentList e<-myEndTag if tagName b == tagName e then return (MakeContentChild b l e) else fail $ "tag don't match "++tagName b++" and "++tagName e myContentList :: Parser [MyHTMLContent] myContentList = (do { r<-myContent; l<-myContentList; return ([r]++l)}) <|> do return [] --myContentList = try (do {r<-myContent; many myComment; l<-myContentList; return ([r]++l)}) myHTML :: Parser [MyHTMLContent] myHTML = do x <- myContentList eof return x
ubuntu11.04でmp600を使う
· 元記事
環境:xubuntu11.04+canon mp600
キヤノン:ダウンロード|ScanGear MP Ver.1.00 for Linux
ここから共通ファイルとmp600用のファイルをrpm形式でダウンロードして
キヤノンよりダウンロードした2つのファイルを~/canonに置いた。
sudo apt-get install alien
sudo apt-get install libglib2.0
sudo apt-get install libgtk2.0
sudo apt-get install libpng3
cd canon
sudo alien -c *.rpm
sudo dpkg -i scangearmp-common_1.00-3_i386.deb
sudo dpkg -i scangearmp-mp600_1.00-2_i386.deb
sudo ln -s /usr/lib/i386-linux-gnu/libtiff.so.4 /usr/lib/libtiff.so.3
sudo /etc/init.d/cups restart
プリンターの電源を入れパソコンに接続した
動いた
参考にしたサイト
Ubuntu日本語フォーラム / プリンター動作報告
の#116、Unpaさんの投稿
やり方が悪いのかもしれませんが、ここで
/etc/cups/printers.confを強引に書き換えないと、プリンタが動きませんでした。
sudo vi /etc/cups/printers.conf
「DeviceURI usb://Canon/MP600」の行の内容を
「DeviceURI cnij_usb:/dev/usb/lp0」に書き換え
ここは必要なかった
JavaScriptでインベーダーゲームを作るーその1
· 元記事
JavaScriptの実行されるタイミングがよく分からない。
SuperCon2011予選
· 元記事
PriorityQueueを使って全ての地点までのk最短路を求めた後にメモ化再帰。
ソースコードの後半は指定されていたテンプレート+入力データのコピーだけ。
/* SuperCon 2011 予選問題C用テンプレート ・解答プログラムはこのテンプレートに従って作成すること. ・解答プログラムは1つのファイルで,チーム名.c という名前にすること. ・入力の方式は,あらかじめ入力ファイル(例:input_sample.txt)を作っ ておき,実行時にファイル名を指定する方式です. */ #include <assert.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> /* ↓以下の範囲は変更可能 */ #include <limits.h> #define HEAPTABLESIZE 4*210*210*210 int heapData[HEAPTABLESIZE]; int heapTable[HEAPTABLESIZE][3]; int heapSize=0; int heapTableSize=HEAPTABLESIZE; int heapTableCur=0; void heapPop(int*,int*,int*); void heapPush(int,int,int); int heapEmpty(); void heapDown(int); void heapUp(int); int rec(int x,int y,int o); void solve(int _h,int _w,int _k,int *ans_len,int *ans_num); int k,w,h; int mv[4][2]={{0,1},{1,0},{0,-1},{-1,0}}; int dist[210][210][210]; int filled[210][210]; int MAP[200+1][200+1][4]; int cache[210][210][210]; int heapEmpty() { return (heapSize==0); } void heapPop(int *c,int *x,int *y) { if(heapSize==0) { *c=-1,*x=-1,*y=-1; } else if(heapSize==1) { int r=heapData[1]; heapData[heapSize]=-1; heapSize=0; *c=heapTable[r][0],*x=heapTable[r][1],*y=heapTable[r][2]; } else { int r=heapData[1]; heapData[1]=heapData[heapSize]; heapSize--; heapDown(1); *c=heapTable[r][0],*x=heapTable[r][1],*y=heapTable[r][2]; } } void heapPush(int c,int x,int y) { heapTableCur=(heapTableCur+1)%heapTableSize; heapTable[heapTableCur][0]=c; heapTable[heapTableCur][1]=x; heapTable[heapTableCur][2]=y; heapSize++; heapData[heapSize]=heapTableCur; heapUp(heapSize); } void heapUp(int k) { int p=k/2; if(k==1) ; else if(heapTable[heapData[k]][0]<heapTable[heapData[p]][0]) { int t=heapData[k]; heapData[k]=heapData[p]; heapData[p]=t; heapUp(p); } else ; } void heapDown(int k) { int n1=2*k,n2=2*k+1; if(heapSize<n1) { ; } else if(heapSize<n2) { if(heapTable[heapData[n1]][0]<heapTable[heapData[k]][0]) { int t=heapData[k]; heapData[k]=heapData[n1]; heapData[n1]=t; heapDown(n1); } else { ; } } else { if(heapTable[heapData[n1]][0]<heapTable[heapData[n2]][0] && heapTable[heapData[n1]][0]<heapTable[heapData[k]][0]) { int t=heapData[k]; heapData[k]=heapData[n1]; heapData[n1]=t; heapDown(n1); } else if(heapTable[heapData[n2]][0]<=heapTable[heapData[n1]][0] && heapTable[heapData[n2]][0]<heapTable[heapData[k]][0]) { int t=heapData[k]; heapData[k]=heapData[n2]; heapData[n2]=t; heapDown(n2); } else { ; } } } int rec(int x,int y,int o) { int pp; int *r=&cache[x][y][o]; if(*r!=-1) ; else if(x==0 && y==0 && o==0) *r=1; else { *r=0; for(pp=0;pp<4;pp++) if(MAP[x][y][pp]!=0) { int xx=x+mv[pp][0],yy=y+mv[pp][1]; int cc=dist[x][y][o]-MAP[x][y][pp]; int bo=0,ce=k-1; while(1<ce-bo) { int me=(bo+ce)/2; if(dist[xx][yy][me]<cc) bo=me; else ce=me; } if(dist[xx][yy][bo]==cc) *r+=rec(xx,yy,bo); else if(dist[xx][yy][ce]==cc) *r+=rec(xx,yy,ce); else ; } } return *r; } void solve(int _h,int _w,int _k,int *ans_len,int *ans_num) { int pp,qq,rr; int c,x,y; int cc,xx,yy; k=_k,w=_w,h=_h; h++,w++; for(pp=0;pp<210;pp++) for(qq=0;qq<210;qq++) { for(rr=0;rr<210;rr++) dist[pp][qq][rr]=INT_MAX; filled[pp][qq]=0; } heapPush(0,0,0); while(!heapEmpty()) { heapPop(&c,&x,&y); if(filled[x][y]==k || (0<filled[x][y] && dist[x][y][filled[x][y]-1]==c)) { ; } else { dist[x][y][filled[x][y]]=c; filled[x][y]++; //if(x!=w-1 || y!=h-1) for(pp=0;pp<4;pp++) if(MAP[x][y][pp]!=0) { cc=c+MAP[x][y][pp]; xx=x+mv[pp][0]; yy=y+mv[pp][1]; //if(!(filled[xx][yy]==k || (0<filled[xx][yy] && dist[xx][yy][filled[xx][yy]-1]==cc))) //{ heapPush(cc,xx,yy); //} } } } memset(cache,-1,sizeof(cache)); if(dist[w-1][h-1][k-1]!=INT_MAX) { *ans_len=dist[w-1][h-1][k-1]; *ans_num=rec(w-1,h-1,k-1); } else { *ans_len=0; *ans_num=0; } } /* ↑上記の範囲は変更可能 */ int main(int argc, char** argv) { int answer_length = -1; /* この変数に k 番目に長い経路の長さを代入してください */ int answer_number = -1; /* この変数に k 番目に長い経路の総数を代入してください */ int m, n, k; int D[200+1][200+1][4]; char* problem_file; clock_t start, end; FILE* fp; int i, x, y; char buf[0xffff]; char* p; if (argc <= 1) { fprintf(stderr, "Enter the input file.\n"); exit(EXIT_FAILURE); } problem_file = argv[1]; fp = fopen(problem_file, "r"); if (fp == NULL) { fprintf(stderr, "Cannot open %s.\n", problem_file); exit(EXIT_FAILURE); } p = fgets(buf, 0xffff, fp); assert(p != 0); m = atoi(strtok(buf, ", \n")); n = atoi(strtok(NULL, ", \n")); k = atoi(strtok(NULL, ", \n")); assert(m > 0 && m <= 200); assert(n > 0 && n <= 200); assert(k > 0 && k <= 200); for (y = 0; y <= n; y++) { p = fgets(buf, 0xffff, fp); assert(p != 0); p = strtok(buf, ", \n"); for (i = 0; i < m; i++) { int length = atoi(p); assert(length >= 0 && length <= 20); D[i][y][1] = length; D[i+1][y][3] = length; p = strtok(NULL, ", \n"); } D[0][y][3] = 0; D[m][y][1] = 0; } for (x = 0; x <= m; x++) { p = fgets(buf, 0xffff, fp); assert(p != 0); p = strtok(buf, ", \n"); for (i = 0; i < n; i++) { int length = atoi(p); assert(length >= 0 && length <= 20); D[x][i][0] = length; D[x][i+1][2] = length; p = strtok(NULL, ", \n"); } D[x][0][2] = 0; D[x][n][0] = 0; } fclose(fp); /* 入力した情報を画面に出力する(デバッグ等に使って下さい) 提出時は削除しないで,このようにコメントアウトすること printf("The input graph\n"); for (i = 2*n; i >= 0; i--) { y = i / 2; if (i % 2 == 0) { printf("+"); for (x = 0; x < m; x++) { assert(D[x][y][1] == D[x+1][y][3]); printf("%d+", D[x][y][1]); } assert(D[0][y][3] == 0); assert(D[m][y][1] == 0); } else if (i > 0) { for (x = 0; x <= m; x++) { assert(D[x][y][0] == D[x][y+1][2]); assert(y != n-1 || D[x][y+1][0] == 0); printf("%d ", D[x][y][0]); } } else { for (x = 0; x <= m; x++) { assert(D[x][y][2] == 0); } } printf("\n"); } printf("\n"); */ /* 時間計測用(デバッグ等に使って下さい) 提出時は削除しないで,このようにコメントアウトすること start = clock(); */ /* ↓以下の範囲は変更可能 */ int pp,qq,rr; for(pp=0;pp<201;pp++) for(qq=0;qq<201;qq++) for(rr=0;rr<4;rr++) MAP[pp][qq][rr]=D[pp][qq][rr]; solve(n,m,k,&answer_length,&answer_number); /* ↑上記の範囲は変更可能 */ /* 時間計測用(デバッグ等に使って下さい) 提出時は削除しないで,このようにコメントアウトすること end = clock(); printf("%s, %f, %d, %d\n", problem_file, (double)(end - start) / CLOCKS_PER_SEC, answer_length, answer_number); */ printf("%s, %d, %d\n", problem_file, answer_length, answer_number); return 0; }
SRM370 div1medium
· 元記事
通信範囲を1づつ大きくしながらメモ化再帰。
#include <algorithm> #include <vector> using namespace std; class ConnectTheCities { public: int n,dist,range; vector<int> pos; int cache[105][55]; int rec(int bP,int k) { //printf("bP=%d k=%d\n",bP,k); int &r=cache[bP][k]; if(r!=-1) ; else if(k==n) { if(dist-range<=bP) r=0; else r=1000000; } else { r=1000000; for(int nP=bP;nP<=min(bP+range,dist);nP++) r=min(r,abs(nP-pos[k])+rec(nP,k+1)); } return r; } int minimalRange(int distance, int funds, vector <int> position) { dist=distance; pos=position; sort(pos.begin(),pos.end()); n=pos.size(); pos.push_back(distance); //printf("n=%d\n",n); for(range=1;range<=distance;range++) { memset(cache,-1,sizeof(cache)); if(rec(0,0)<=funds) return range; } } };
無題
· 元記事
ややこしすぎて、なんで通ったのか分からない。
#include <algorithm> #include <string> #include <vector> #include <iostream> #include <sstream> #include <cstdio> #include <cstring> #include <cmath> #include <set> #include <map> #include <climits> #include <cfloat> using namespace std; int K,gN; int E[40]; int G[16][16]; double C[40][40]; double cache[16][1<<16]; void init() { for(int i=0;i<40;i++) for(int j=0;j<40;j++) if(i<j) C[i][j]=0; else if(j==0) C[i][j]=1; else C[i][j]=C[i-1][j]+C[i-1][j-1]; for(int i=0;i<16;i++) for(int j=0;j<1<<16;j++) cache[i][j]=-2.0; } void makeG(vector<string> input) { for(int i=0;i<16;i++) for(int j=0;j<16;j++) G[i][j]=0; gN=1; set<int> masaoFriends; for(int i=0;i<input.size();i++) { int a; stringstream ss(input[i]); vector<int> v; while(ss>>a) v.push_back(a); if(i==0) { masaoFriends.insert(1); for(int j=0;j<v.size();j++) masaoFriends.insert(v[j]); for(int j=0;j<v.size();j++) { int p=distance(masaoFriends.begin(),masaoFriends.find(i+1)); int q=distance(masaoFriends.begin(),masaoFriends.find(v[j])); G[p][q]=G[q][p]=1; } } else if(masaoFriends.find(i+1)!=masaoFriends.end()) { gN++; for(int j=0;j<v.size();j++) if(masaoFriends.find(v[j])!=masaoFriends.end()) { int p=distance(masaoFriends.begin(),masaoFriends.find(i+1)); int q=distance(masaoFriends.begin(),masaoFriends.find(v[j])); G[p][q]=G[p][q]=1; } } E[distance(masaoFriends.begin(),masaoFriends.find(i+1))]=v.size(); } /*printf("masaoFriends ="); for(set<int>::iterator it=masaoFriends.begin();it!=masaoFriends.end();it++) printf("%d ",*it); printf("\n"); for(int i=0;i<gN;i++) { for(int j=0;j<gN;j++) printf("%d ",G[i][j]); printf("\n"); }*/ } double rec(int pos,int left) { //printf("rec(%d,%d)\n",pos,left); double &r=cache[pos][left]; if(-1.0<r) ; else if(left==0) r=1.0; else { r=0.0; vector<double> prob; for(int i=0;i<gN;i++) if(G[pos][i] && ((1<<i)&left)) prob.push_back(rec(i,left^(1<<i))); sort(prob.begin(),prob.end()); reverse(prob.begin(),prob.end()); /*printf("prob="); for(int i=0;i<prob.size();i++) printf("%lf ",prob[i]); printf("\n");*/ if(K<E[pos]) { double p=1.0; for(int i=0;i<prob.size();i++) { r+=(p-C[E[pos]-i-1][K]/C[E[pos]][K])*prob[i]; p=C[E[pos]-i-1][K]/C[E[pos]][K]; } } else if(0<prob.size()) r+=prob[0]; } return r; } class FriendTour { public: double tourProbability(vector<string> input,int _K) { K=_K; makeG(input); init(); return rec(0,(1<<gN)-1-1); } };
SRM331 div1 medium
· 元記事
2^10は使わなかった。
#include <algorithm> #include <vector> using namespace std; class Shopping { public: int minNumber(int X, vector <int> values) { sort(values.begin(),values.end()); if(values[0]!=1) { return -1; } else { int r=1; int s; for(s=1;s<X;) { int n=0; while(n+1<values.size() && values[n+1]<=s+1) n++; s+=values[n]; r++; } return r; } } };
SRM337 div1 medium
· 元記事
ALGORITHM NOTE ヒストグラム中の最大の長方形の面積
または
Amazon.co.jp: プログラミングコンテストチャレンジブック: 秋葉 拓哉, 岩田 陽一, 北川 宜稔: 本
の280ページ、スタックの利用を参照。
#include <vector> #include <stack> using namespace std; typedef long long LL; class BuildingAdvertise { public: int n; vector<int> building; vector<int> L,R; void makeR(vector<int> h) { building=vector<int>(n,0); int j=0; int m=h.size(); for(int i=0;i<n;i++) { building[i]=h[j]; int s=(j+1)%m; h[j]=((h[j]^h[s])+13)%835454957; j=s; } } LL getMaxArea(vector <int> h, int _n) { n=_n; makeR(h); L=vector<int>(n,0); R=vector<int>(n,0); stack<int> sta; for(int i=0;i<n;i++) { while(!sta.empty() && building[i]<=building[sta.top()]) sta.pop(); if(sta.empty()) L[i]=0; else L[i]=sta.top()+1; sta.push(i); } while(!sta.empty()) sta.pop(); for(int i=n-1;0<=i;i--) { while(!sta.empty() && building[i]<=building[sta.top()]) sta.pop(); if(sta.empty()) R[i]=n; else R[i]=sta.top(); sta.push(i); } LL ans=0; for(int i=0;i<n;i++) ans=max(ans,(LL)(R[i]-L[i])*(LL)building[i]); return ans; } };
SRM367 div1medium
· 元記事
問題概要
あるデバイスに送らななければならないデータがその始まりのアドレスと大きさの形で渡される。
データはパケットに分けて送らなければならない。
そのパケットはメインデータとオーバーヘッド部分から成る。
このとき送らなければならない最小のバイト数を求めよ。
要はどこからどこまでのデータをまとめて送るかということが大切。
http://www.topcoder.com/tc?module=Static&d1;=match_editorials&d2;=srm367
↑の中にあるサンプルソースコードが非常にわかり易い。
#include <vector> using namespace std; typedef long long LL; class DeviceProgramming { public: LL neededBytes(LL maxPacketSize,LL overhead,LL size) { LL maxInfo=maxPacketSize-overhead; LL packetsNeeded=((size%maxInfo==0)?size/maxInfo:size/maxInfo+1); return size+packetsNeeded*overhead; } LL minBytes(vector <int> offset, vector <int> size, int maxPacketSize, int overhead) { int n=offset.size(); LL DP[60]; vector<int> st,fn; for(int i=0;i<n;i++) { st.push_back(offset[i]); fn.push_back(offset[i]+size[i]-1); } for(int i=0;i<n;i++) for(int j=i+1;j<n;j++) if(st[i]>st[j]) { swap(st[i],st[j]); swap(fn[i],fn[j]); } for(int i=0;i<n;i++) { DP[i]=neededBytes(maxPacketSize,overhead,fn[i]-st[0]+1); for(int j=0;j<i;j++) DP[i]=min(DP[i],DP[j]+neededBytes(maxPacketSize,overhead,fn[i]-st[j+1]+1)); } return DP[n-1]; } };
SRM384 div1 medium
· 元記事
問題要約
ーーーーーーーーーーーー
n人の人が円形に並んでいて、
時計回りの順にボールを任意の他の人に向かって投げる。
ボールがあたった人は円から離脱し、
番号iの人が投げたボールが当たる確率はprobably[i]である。
この時円形に並んでいる人が1人になるまでのボールが投げられる回数の最小の期待値を求めよ。
ーーーーーーーーーーーー
まず、制限の
"Each element of probability will be between 10 and 100, inclusive."
に解法のヒントを見つけた。
10%からということは10000回くらいボールを投げ続けたら
失敗する確率は最悪でも 0.9^10000≒0 に収束する。
それならこの問題は rec(誰が残っている(2^6),誰の番(6),何回目(10000))でメモ化再帰。
つまずいたのはボールを投げるときに誰に向かって投げるかということ。
様々なケースが考えられるので投げられる全員に投げてみて一番期待値が小さいものを選ぶと良い。
#include <algorithm> #include <string> #include <vector> #include <iostream> #include <sstream> #include <cstdio> #include <cstring> #include <cmath> #include <set> #include <map> #include <climits> #include <cfloat> using namespace std; double cache[(1<<6)+1][6+1][10000+10]; class SchoolTrip { public: int n; vector<double> p; int popCount(int _mask) { int r=0; for(int i=0;i<28;i++) if(_mask & (1<<i)) r++; return r; } double rec(int left,int turn,int time) { double &r=cache[left][turn][time]; if(-1.0<r) ; else if(time==10000 || popCount(left)==1) r=0.0; else { r=1.0; int ln=popCount(left)-1; double minThrow=FLT_MAX; for(int hit=turn+1;;hit++) { hit%=n; if(hit==turn) break; int sleft=left^(1<<hit); for(int nTurn=turn+1;;nTurn++) { nTurn%=n; if(sleft&(1<<nTurn)) { minThrow=min(minThrow,p[turn]*rec(sleft,nTurn,time+1)); break; } } } r+=minThrow; for(int nTurn=turn+1;;nTurn++) { nTurn%=n; if(left & (1<<nTurn)) { r+=(1.0-p[turn])*rec(left,nTurn,time+1); break; } } } return r; } double duration(vector <int> probability) { n=probability.size(); p.clear(); for(int i=0;i<n;i++) p.push_back(probability[i]/100.0); for(int i=0;i<(1<<6);i++) for(int j=0;j<6;j++) for(int k=0;k<10000;k++) cache[i][j][k]=-2.0; return rec((1<<n)-1,0,0); } };
SRM397 div1 medium
· 元記事
k乗和の公式を使う。
求め方は数学Bの教科書に載っている。
#include <algorithm> #include <string> #include <vector> #include <iostream> #include <sstream> #include <cstdio> #include <cstring> #include <cmath> #include <set> #include <map> #include <climits> using namespace std; typedef long long LL; static const LL MOD=1000000007; LL ADD(LL x,LL y){ return (x+y)%MOD; } LL SUB(LL x,LL y){ return (x-y+MOD)%MOD; } LL MUL(LL x,LL y){ return ((x*y)%MOD+MOD)%MOD; } LL POW(LL x,LL e) { LL r=1; for(;e;x=MUL(x,x),e>>=1) if(e&1) r=MUL(r,x); return r; } LL DIV(LL x,LL y){ return MUL(x,POW(y,MOD-2)); } LL COMB(LL n,LL k) { LL r=1; for(LL i=1;i<=k;i++) r=DIV(MUL(r,n-i+1),i); return r; } class SumOfPowers { public: LL n,k; LL bin[60][60]; LL cache[60]; LL rec(int p) { LL &r=cache[p]; if(r!=-1) ; else if(p==0) r=n; else if(p==1) r=n*(n+1)/2%MOD; else { r=(POW(n+1,p+1)-1)%MOD; for(int i=0;i<p;i++) { r-=MUL(bin[p+1][i],rec(i)); r=(r%MOD+MOD)%MOD; } r=DIV(r,bin[p+1][p]); } return r; } int value(int _n, int _k) { n=_n,k=_k; for(int i=0;i<60;i++) for(int j=0;j<60;j++) if(i<j) bin[i][j]=0; else if(j==0) bin[i][j]=1; else bin[i][j]=(bin[i-1][j]+bin[i-1][j-1])%MOD; /*for(int i=1;i<10;i++) for(int j=1;j<=i;j++) printf("bin[%d][%d]=%lld\n",i,j,bin[i][j]);*/ memset(cache,-1,sizeof(cache)); return rec(k); } };
SRM508div1
· 元記事
なにそれオイシーの?
SRM388div1 medium
· 元記事
使うギターを全探索するだけ。
#include <algorithm> #include <string> #include <vector> #include <set> using namespace std; class GuitarConcert { public: vector<string> comp(vector<string> vs1,vector<string> vs2) { if(vs1.size()==vs2.size()) return (vs1<vs2)?vs1:vs2; else return (vs1.size()<vs2.size())?vs1:vs2; } vector <string> buyGuitars(vector <string> guitarNames, vector <string> guitarSongs) { int nG=guitarNames.size(),nS=guitarSongs[0].size(),maxPlay=0; vector<string> ans; for(int use=0;use<(1<<nG);use++) { set<int> se; vector<string> vs; for(int i=0;i<nG;i++) if(use & (1<<i)) { vs.push_back(guitarNames[i]); for(int j=0;j<nS;j++) if(guitarSongs[i][j]=='Y') se.insert(j); } sort(vs.begin(),vs.end()); if(maxPlay<se.size()) { maxPlay=se.size(); ans=vs; } else if(maxPlay==se.size()) ans=comp(ans,vs); else ; } return ans; } };
SRM388div1 medium
· 元記事
分からなかった。
ビットを使って部分集合を求めると結構速いらしい。
このDPの計算量はそれぞれのビットについて
(mask:0,sub:0),(mask:1,sub:0),(mask:1,sub:1)
の場合があるので3^nなんだって。
2^n*2^nしか思いつかなかった。
#include <vector> using namespace std; int OK[1<<15]; int DP[1<<15]; class InformFriends { public: int maximumGroups(vector<string> _friends) { int n; vector<int> friends; n=_friends.size(); for(int i=0;i<n;i++) { int a=(1<<i); for(int j=0;j<n;j++) if(_friends[i][j]=='Y') a+=(1<<j); friends.push_back(a); } for(int i=0;i<(1<<n);i++) { int a=0; for(int j=0;j<n;j++) if(i&(1<<j)) a|=friends[j]; OK[i]=(a==(1<<n)-1); } for(int mask=0;mask<(1<<n);mask++) { DP[mask]=0; int m=mask; while(m!=0) { if(OK[m]) DP[mask]=max(DP[mask],DP[mask^m]+1); m--; m&=mask; } } return DP[(1<<n)-1]; } };
SRM386div1 medium
· 元記事
凸な図形=三角形の集まりなので
rec(どの座標を使ったか(=mask)、今どの座標か(=k))は
kを含む三角形を(i,j,k)の面積をsとすると
s+rec(maskにi,j,kを追加した物,k+1)
の内最小化のものを返せば良い。
あとはそれをメモ化再帰して終わり。
三角形の面積を求めるにはヘロンの公式が便利。
#include <vector> #include <cmath> #include <cfloat> using namespace std; double cache[(1<<15)][20]; class PolygonCover { public: int n; vector<double> x; vector<double> y; double S(int v1,int v2,int v3) { double e1,e2,e3,s,x1,y1,x2,y2,x3,y3; x1=x[v1]-x[v2]; y1=y[v1]-y[v2]; x2=x[v2]-x[v3]; y2=y[v2]-y[v3]; x3=x[v3]-x[v1]; y3=y[v3]-y[v1]; e1=sqrt(x1*x1+y1*y1); e2=sqrt(x2*x2+y2*y2); e3=sqrt(x3*x3+y3*y3); s=(e1+e2+e3)/2; return sqrt(s*(s-e1)*(s-e2)*(s-e3)); } double rec(int mask,int k) { double &r=cache[mask][k]; if(-1<r) return r; else if(k==n) r=0.0; else if(mask & (1<<k)) r=rec(mask,k+1); else { r=DBL_MAX; for(int i=0;i<n;i++) for(int j=i+1;j<n;j++) if(i!=k && j!=k) { int m=(mask|(1<<i)|(1<<j)|(1<<k)); r=min(r,S(i,j,k)+rec(m,k+1)); } } return r; } double getArea(vector<int> _x, vector<int> _y) { x.clear(); y.clear(); n=_x.size(); for(int i=0;i<n;i++) { x.push_back(_x[i]); y.push_back(_y[i]); } for(int i=0;i<(1<<n);i++) for(int j=0;j<n;j++) cache[i][j]=-2; return rec(0,0); } };
SRM329div1 medium
· 元記事
最大値の最小化なのでセオリー通りに二分探索。
#include <algorithm> #include <string> #include <vector> #include <sstream> using namespace std; class LogCutter { public: int n; vector<int> canCut; bool check(int L,int C,int mid) { int beforeCut=0; while(mid<L-canCut[beforeCut] && 0<C) { if(mid<canCut[beforeCut+1]-canCut[beforeCut]) return false; int nextCut=beforeCut+1; while(nextCut+1<n && canCut[nextCut+1]-canCut[beforeCut]<=mid) nextCut++; C--; beforeCut=nextCut; } return (L-canCut[beforeCut]<=mid); } string bestCut(int L, int A, int K, int C) { long long ll=L,aa=A; canCut.clear(); for(long long i=1;i<=K;i++) canCut.push_back((aa*i)%(ll-1)+1); canCut.push_back(0); canCut.push_back(L); sort(canCut.begin(),canCut.end()); canCut.erase(unique(canCut.begin(),canCut.end()),canCut.end()); n=canCut.size(); int ceil=L,bottom=1; while(1<ceil-bottom) { int mid=(ceil+bottom)/2; if(check(L,C,mid)) ceil=mid; else bottom=mid; } int opt=(check(L,C,bottom)?bottom:ceil); int bc=n-1,fc,fisrtCut; while(1) { fc=bc-1; while(canCut[bc]-canCut[fc-1]<=opt && 0<=fc-1) fc--; if(fc==0) { if(0<C) fisrtCut=1; else fisrtCut=bc; break; } bc=fc; C--; } stringstream ans; ans << opt << " " << canCut[fisrtCut]; return ans.str(); } };
SRM410 div1 medium
· 元記事
#include <algorithm> #include <vector> #include <cstdio> #include <cstring> #include <climits> using namespace std; typedef long long LL; int n; LL k; int range[55][55][2][2]; LL cache[55][55][2]; class ContiguousCache { public: LL intersect(int a1,int a2,int b1,int b2) { if(a1<=b1 && b1<=a2 && a2<=b2) return a2-b1+1; else if(a1<=b1 && b2<=a2) return b2-b1+1; else if(b1<=a1 && a2<=b2) return a2-a1+1; else if(b1<=a1 && a1<=b2 && b2<=a2) return b2-a1+1; else return 0; } LL rec(int a1,int a2,int slide) { int b1=a2+1,b2,nextSlide; LL &r=cache[a1][a2][slide]; if(r!=-1) ; else if(b1==n) r=0; else { r=LLONG_MAX; for(b2=b1;b2<n && range[b1][b2][0][0]!=-1;b2++) for(nextSlide=0;nextSlide<2;nextSlide++) r=min(r,k-intersect(range[a1][a2][slide][0],range[a1][a2][slide][1],range[b1][b2][nextSlide][0],range[b1][b2][nextSlide][1])+rec(b1,b2,nextSlide)); } return r; } LL minimumReads(int _n,int _k,vector<int> addresses) { reverse(addresses.begin(),addresses.end()); addresses.push_back(INT_MIN/2); reverse(addresses.begin(),addresses.end()); n=addresses.size(),k=_k; for(int i=0;i<n;i++) for(int j=0;j<n;j++) { LL mi=INT_MAX/2,ma=INT_MIN/2; for(int l=min(i,j);l<=max(i,j);l++) { mi=min(mi,(LL)addresses[l]); ma=max(ma,(LL)addresses[l]); } range[i][j][0][0]=-1,range[i][j][0][1]=-1; range[i][j][1][0]=-1,range[i][j][1][1]=-1; if(ma-mi<k) { range[i][j][0][0]=mi,range[i][j][0][1]=mi+k-1; range[i][j][1][0]=ma-k+1,range[i][j][1][1]=ma; } } memset(cache,-1,sizeof(cache)); return rec(0,0,0); } };
SRM439 div1 medium
· 元記事
メモ化再帰。
難しくて解説みた。
#include <string> #include <vector> #include <cstdio> #include <cstring> using namespace std; typedef long long LL; LL DP[(1<<13)][2][15][15]; class PalindromePhrases { public: int n; vector<string> words; bool isPalindome(string s) { for(int i=0;i<s.size();i++) if(s[i]!=s[s.size()-1-i]) return false; return true; } LL rec(int left,int isLeft,int id,int len) { LL &r=DP[left][isLeft][id][len]; if(r!=-1) ; else if(isLeft) { r=0; int pos=words[id].size()-len; string s=words[id].substr(pos); if(isPalindome(s)) r++; for(int next=0;next<n;next++) if(left & (1<<next)) if(len<words[next].size()) { int i; for(i=0;i<len;i++) if(words[id][pos+i]!=words[next][words[next].size()-1-i]) break; if(i==len) r+=rec(left^(1<<next),0,next,words[next].size()-i); } else { int i; for(i=0;i<words[next].size();i++) if(words[id][pos+i]!=words[next][words[next].size()-1-i]) break; if(i==words[next].size()) r+=rec(left^(1<<next),1,id,len-i); } } else { r=0; int pos=len-1; string s=words[id].substr(0,len); if(isPalindome(s)) r++; for(int next=0;next<n;next++) if(left & (1<<next)) if(len<words[next].size()) { int i; for(i=0;i<len;i++) if(words[id][i]!=words[next][len-1-i]) break; if(i==len) r+=rec(left^(1<<next),1,next,words[next].size()-i); } else { int i; for(i=0;i<words[next].size();i++) if(words[id][pos-i]!=words[next][i]) break; if(i==words[next].size()) r+=rec(left^(1<<next),0,id,len-i); } } return r; } LL getAmount(vector <string> _words) { words=_words; n=words.size(); memset(DP,-1,sizeof(DP)); LL ans=0; for(int i=0;i<n;i++) ans+=rec(((1<<n)-1)^(1<<i),1,i,words[i].size()); return ans; } };
Google Code Jam Qualification Round
· 元記事
満点で予選通過した。
Round1は中間テストの真っ最中だけど、頑張って出るかもしれない。
SRM479 div1 medium
· 元記事
二部探索+ダイクストラ。
#include <algorithm> #include <string> #include <vector> #include <sstream> #include <queue> #include <utility> using namespace std; typedef long long LL; int n; LL limit; LL INF=((LL)1<<LL(50)); LL visited[500]; vector<int> F[500]; vector<int> T[500]; vector<int> P[500]; vector<int> D[500]; class TheAirTripDivOne { public: bool check(LL wait) { for(int i=0;i<n;i++) visited[i]=INF; priority_queue<pair<LL,int>,vector<pair<LL,int> >,greater<pair<LL,int> > > que; que.push(make_pair(0,0)); bool succes=false; while(!que.empty()) { LL now=que.top().first; int pos=que.top().second; que.pop(); if(visited[pos]<now || limit<now) continue; else if(pos==n-1) { succes=true; break; } else { visited[pos]=now; if(pos!=0) now+=wait; else ; for(int i=0;i<F[pos].size();i++) if(F[pos][i]!=INF) { LL ne=F[pos][i]+((now<F[pos][i])?0: (now-F[pos][i]+P[pos][i]-1)/P[pos][i]*P[pos][i])+T[pos][i]; que.push(make_pair(ne,D[pos][i])); } } } return succes; } int find(int _n, vector <string> flights, int time) { limit=time; n=_n; for(int i=0;i<500;i++) F[i].clear(),T[i].clear(),P[i].clear(),D[i].clear(); string input; for(int i=0;i<flights.size();i++) { for(int j=0;j<flights[i].size();j++) if(flights[i][j]==',') flights[i][j]=' '; input+=flights[i]; } stringstream ss(input); int a,b,f,t,p; while(ss >> a >> b >> f >> t >> p) { a--,b--; F[a].push_back(f); T[a].push_back(t); P[a].push_back(p); D[a].push_back(b); } int ceil=time,bottom=1; while(5<ceil-bottom) { int wait=(ceil+bottom)/2; if(check(wait)) bottom=wait; else ceil=wait; } for(int i=ceil;bottom<=i;i--) if(check(i)) return i; return -1; } };
SRM464 div1 medium
· 元記事
二部探索+2SATらしい。
#include <vector> #include <cstdio> #include <cstring> using namespace std; class ColorfulDecoration { public: int n; int len; int dist[110][110]; int G[110][110]; int ABS(int n) { return (n<0)?-n:n; } bool check() { memset(G,0,sizeof(G)); for(int i=0;i<2*n;i++) for(int j=0;j<2*n;j++) if(i%n!=j%n && dist[i][j]<len) { int ii=(i<n)?i+n:i-n; int jj=(j<n)?j+n:j-n; G[i][jj]=1; } for(int i=0;i<2*n;i++) G[i][i]=1; for(int k=0;k<2*n;k++) for(int i=0;i<2*n;i++) for(int j=0;j<2*n;j++) if(G[i][k]==1 && G[k][j]==1) G[i][j]=1; for(int i=0;i<n;i++) if(G[i][i+n] && G[i+n][i]) return false; return true; } int getMaximum(vector <int> xa, vector <int> ya, vector <int> xb, vector <int> yb) { n=xa.size(); for(int i=0;i<2*n;i++) for(int j=0;j<2*n;j++) { int x1=(i<n)?xa[i]:xb[i-n]; int y1=(i<n)?ya[i]:yb[i-n]; int x2=(j<n)?xa[j]:xb[j-n]; int y2=(j<n)?ya[j]:yb[j-n]; dist[i][j]=max(ABS(x1-x2),ABS(y1-y2)); //printf("dist[%d][%d]=%d\n",i,j,dist[i][j]); } int ceil=1000000000,bottom=0; while(5<ceil-bottom) { len=(ceil+bottom)/2; if(check()) bottom=len; else ceil=len; } for(len=ceil;bottom<=len;len--) { if(check()) return len; } return 0; } };
SRM385 div1 medium
· 元記事
眠い!
どの行とどの列をひっくり返したかと位置を状態としてBFS。
#include <string> #include <vector> #include <sstream> #include <queue> #include <utility> using namespace std; int mv[4][2]={{-1,0},{1,0},{0,-1},{0,1}}; int dist[(1<<15)][10][10]; class TurningMaze { public: bool can(char c,char d) { if(c=='B' || d=='B') return false; else if(c==d) return true; else if(c=='A' || d=='A') return true; else return false; } int minTime(vector <string> maze) { printf("\n"); fflush(stdout); int h=maze.size(),w=maze[0].size(); for(int i=0;i<(1<<15);i++) for(int j=0;j<10;j++) for(int k=0;k<10;k++) dist[i][j][k]=(1<<28); queue<pair<pair<int,int>,int> > que; que.push(make_pair(make_pair(0,0),0)); while(!que.empty()) { int m=que.front().first.first; int y=que.front().first.second/w; int x=que.front().first.second%w; int d=que.front().second; que.pop(); //printf("m=%d y=%d x=%d d=%d\n",m,y,x,d); //fflush(stdout); if(0<=y && y<h && 0<=x && x<w && d<dist[m][y][x]) { //printf("d<dist[m][y][x]\n"); //fflush(stdout); if(y==h-1 && x==w-1) return d; dist[m][y][x]=d; vector<string> vs=maze; for(int i=0;i<h;i++) if(m&(1<<i)) for(int j=0;j<w;j++) { char c=vs[i][j]; if(c=='C') vs[i][j]='D'; if(c=='D') vs[i][j]='C'; } for(int i=0;i<w;i++) if(m&(1<<i+h)) for(int j=0;j<h;j++) { char c=vs[j][i]; if(c=='C') vs[j][i]='D'; if(c=='D') vs[j][i]='C'; } //printf("convert\n"); //fflush(stdout); for(int i=0;i<4;i++) { int yy=y+mv[i][0],xx=x+mv[i][1]; if(i<2 && 0<=yy && yy<h && (vs[y][x]=='C' || vs[y][x]=='A') && (vs[yy][xx]=='C' || vs[yy][xx]=='A')) que.push(make_pair(make_pair(m,yy*w+xx),d+1)); if(1<i && 0<=xx && xx<w && (vs[y][x]=='D' || vs[y][x]=='A') && (vs[yy][xx]=='D' || vs[yy][xx]=='A')) que.push(make_pair(make_pair(m,yy*w+xx),d+1)); } int mm=m; mm=(mm^(1<<y)); mm=(mm^(1<<(x+h))); que.push(make_pair(make_pair(mm,y*w+x),d+1)); } } return -1; } };
SRM414 div1 medium
· 元記事
貪欲でOK。
#include <string> #include <vector> using namespace std; class StringInterspersal { public: string minimum(vector <string> W) { string ans; int n=W.size(); int s=0; for(int i=0;i<n;i++) s+=W[i].size(); vector<int> used(n,0); while(ans.size()<s) { char c='Z'+1; int p; for(int i=0;i<n;i++) if(used[i]<W[i].size()) { if(W[i][used[i]]<c) c=W[i][used[i]],p=i; else if(W[i][used[i]]==c) { int j; for(j=0;j<min(W[p].size()-used[p],W[i].size()-used[i]);j++) if(W[p][j+used[p]]!=W[i][j+used[i]]) break; if(j+used[p]==W[p].size()) p=i; else if(j+used[i]==W[i].size()) ; else if(W[p][j+used[p]]<W[i][j+used[i]]) ; else p=i; } } ans+=c; used[p]++; } return ans; } };
SRM300 div1 medium
· 元記事
calc(n)をn以下のjumpyNumを求める関数とする
答えはcalc(high)-calc(low-1)
ーーーーーーーーーーーーcalc(n)の説明ーーーーーーーーーーーー
数字を文字列にして考える
n=123456789のとき
m=12345....なら、mはnにpos=4(0-indexed)までマッチしているということにする
DP[match][pos][digit]を数を左から右に考えていったとき、
場所posがdigit(0-9)なら何通りのjumpyNumが存在するかとしてDPする。
(matchはpos=0からpos=pos-1までがマッチしているかどうかを格納する)
#include <string> #include <sstream> using namespace std; int ABS(int n) { return (n<0)?-n:n; } class JumpyNum { public: int calc(int input) { stringstream ss; ss<<input; string num=ss.str(); int n=num.size(); int DP[2][15][10]; for(int pos=n-1;0<=pos;pos--) for(int digit=0;digit<10;digit++) for(int match=0;match<2;match++) if(pos==n-1) { if(match) DP[match][pos][digit]=(digit<=num[pos]-'0')?1:0; else DP[match][pos][digit]=1; } else { if(match) { DP[match][pos][digit]=0; if(digit<num[pos]-'0') for(int nd=0;nd<10;nd++) { if(2<=ABS(digit-nd)) DP[match][pos][digit]+=DP[0][pos+1][nd]; } else if(digit==num[pos]-'0') for(int nd=0;nd<10;nd++) { if(2<=ABS(digit-nd)) DP[match][pos][digit]+=DP[1][pos+1][nd]; } else ; } else { DP[match][pos][digit]=0; for(int nd=0;nd<10;nd++) if(2<=ABS(digit-nd)) DP[match][pos][digit]+=DP[0][pos+1][nd]; } } int ans=0; for(int len=1;len<=n;len++) { if(len==n) { for(int first=1;first<num[0]-'0';first++) ans+=DP[0][0][first]; if(0<num[0]-'0') ans+=DP[1][0][num[0]-'0']; } else { for(int first=1;first<10;first++) ans+=DP[0][n-len][first]; } } return ans; } int howMany(int low, int high) { return calc(high)-calc(low-1); } };
SRM505 div1 easy
· 元記事
二つの列に一箇所でも共通点があれば片方でしか分かっていない情報も分かるようななる。
行についても同様。
#include <string> #include <vector> using namespace std; class RectangleArea { public: int h,w; vector<string> vs; void check() { for(int i=0;i<h;i++) for(int j=i+1;j<h;j++) { int k; for(k=0;k<w;k++) if(vs[i][k]=='Y' && vs[j][k]=='Y') break; if(k<w) for(int k=0;k<w;k++) if(vs[i][k]=='Y' || vs[j][k]=='Y') vs[i][k]=vs[j][k]='Y'; } for(int i=0;i<w;i++) for(int j=i+1;j<w;j++) { int k; for(k=0;k<h;k++) if(vs[k][i]=='Y' && vs[k][j]=='Y') break; if(k<h) for(int k=0;k<h;k++) if(vs[k][i]=='Y' || vs[k][j]=='Y') vs[k][i]=vs[k][j]='Y'; } for(int i=0;i<h;i++) for(int j=i+1;j<h;j++) { int k; for(k=0;k<w;k++) if(vs[i][k]=='Y' && vs[j][k]=='Y') break; if(k<w) for(int k=0;k<w;k++) if(vs[i][k]=='Y' || vs[j][k]=='Y') vs[i][k]=vs[j][k]='Y'; } for(int i=0;i<w;i++) for(int j=i+1;j<w;j++) { int k; for(k=0;k<h;k++) if(vs[k][i]=='Y' && vs[k][j]=='Y') break; if(k<h) for(int k=0;k<h;k++) if(vs[k][i]=='Y' || vs[k][j]=='Y') vs[k][i]=vs[k][j]='Y'; } return; } int minimumQueries(vector <string> known) { vs=known; h=vs.size(),w=vs[0].size(); check(); int ans=0; while(1) { bool end=true; int y,x; for(int i=0;i<h;i++) for(int j=0;j<w;j++) if(vs[i][j]=='N') { y=i,x=j; end=false; break; } if(end) break; ans++; vs[y][x]='Y'; check(); } return ans; } };
SRM505 div1 感想
· 元記事
OXX
1457→1590
亀岡
· 元記事
SRM470 div1 medium
· 元記事
解けなっかた人が解説しても仕方がないのでぐぐればいいと思うよ。
全探索。
#include <vector> using namespace std; class DrawingLines { public: double countLineCrossings(int n, vector <int> startDot, vector <int> endDot) { vector<int> ceil(n,-1); int m=startDot.size(); for(int i=0;i<m;i++) ceil[startDot[i]-1]=endDot[i]-1; double ans=0.0; for(int i=0;i<n;i++) for(int j=i+1;j<n;j++) if(ceil[i]==-1 && ceil[j]==-1) ans+=0.5; else if(ceil[i]==-1 && ceil[j]!=-1) { int c=n-ceil[j]-1; for(int k=0;k<m;k++) if(ceil[j]<endDot[k]-1) c--; ans+=double(c)/double(n-m); } else if(ceil[i]!=-1 && ceil[j]==-1) { int c=ceil[i]; for(int k=0;k<m;k++) if(endDot[k]-1<ceil[i]) c--; ans+=double(c)/double(n-m); } else ans+=(ceil[j]<ceil[i])?1:0; return ans; } };
SRM324 div1 medium
· 元記事
全部一箇所に集めればいい。
マンハッタン距離ではxとyは分離できるので
集める場所のx座標の候補は初期位置のx座標
集める場所のy座標の候補は初期位置のy座標
で全探索。
#include <limits.h> using namespace std; class TournamentPlan { public: int ABS(int n) { return (0<n)?n:-n; } int getTravelDistance(vector <int> street, vector <int> avenue) { int ans=INT_MAX,n=street.size(); for(int s=0;s<n;s++) for(int a=0;a<n;a++) { int r=0; for(int i=0;i<n;i++) r+=ABS(street[s]-street[i])+ABS(avenue[a]-avenue[i]); ans=min(ans,r); } return ans; } };
SRM328 div1 medium
· 元記事
TopCoder Statistics
↑のテキトー日本語訳
最終的にグラフはそれぞれに一つずつmarkedを含む部分グラフに分けられる。
markedな頂点A,B(AとBの間には他のmarkedな頂点はない)を考える。
AとBを分けるときに使う辺はAとBの間で最もコストの小さい辺である。
#include <string> #include <vector> #include <sstream> using namespace std; int occupied[50]; int cost[50][50]; int INF=(1<<28); class BlockEnemy { public: int N; bool DFS(int here,int from) { if(occupied[here]) return true; for(int to=0;to<N;to++) if(to!=from && cost[here][to]!=INF && DFS(to,here)) return true; return false; } int minEffort(int _N, vector <string> roads, vector <int> occupiedTowns) { N=_N; int rN=roads.size(); vector<int> a(rN),b(rN),e(rN); for(int i=0;i<N;i++) for(int j=0;j<N;j++) cost[i][j]=INF; for(int i=0;i<rN;i++) { stringstream ss(roads[i]); ss >> a[i] >> b[i] >> e[i]; cost[a[i]][b[i]]=cost[b[i]][a[i]]=e[i]; } for(int i=0;i<rN;i++) for(int j=i+1;j<rN;j++) if(e[j]<e[i]) swap(a[i],a[j]),swap(b[i],b[j]),swap(e[i],e[j]); for(int i=0;i<N;i++) occupied[i]=false; for(int i=0;i<occupiedTowns.size();i++) occupied[occupiedTowns[i]]=true; int ans=0; for(int i=0;i<rN;i++) if(DFS(a[i],b[i]) && DFS(b[i],a[i])) ans+=e[i],cost[a[i]][b[i]]=cost[b[i]][a[i]]=INF; return ans; } };
SRM407 div1 medium
· 元記事
map
どちらの番かという情報は”どの頂点が残っている”から得ることができる。
#include <map> #include <vector> #include <float.h> #include <cmath> using namespace std; int n; double point[(1<<13)]; map<int,double> cache; int popCount(int _mask) { int r=0; for(int i=0;i<28;i++) if(_mask & (1<<i)) r++; return r; } double rec(int left,int red) { int key=(left<<13)+red; if(cache.find(key)!=cache.end()) return cache[key]; else if(left==0) return cache[key]=point[red]; else { int turn=n-popCount(left); if(turn%2==0) { double r=-10.0; for(int i=0;i<n;i++) if(left&(1<<i)) r=max(r,rec(left^(1<<i),red)); return cache[key]=r; } else { double r=DBL_MAX; for(int i=0;i<n;i++) if(left&(1<<i)) r=min(r,rec(left^(1<<i),red^(1<<i))); return cache[key]=r; } } } class PointsGame { public: double gameValue(vector <int> pointsX, vector <int> pointsY) { n=pointsX.size(); cache.clear(); for(int red=0;red<(1<<n);red++) if(popCount(red)==(n+1)/2) { point[red]=0.0; for(int i=0;i<n;i++) for(int j=i+1;j<n;j++) if((0<(red&(1<<i)))^(0<(red&(1<<j)))) { double xd=pointsX[i]-pointsX[j]; double yd=pointsY[i]-pointsY[j]; point[red]+=sqrt(xd*xd+yd*yd); } } return rec((1<<n)-1,(1<<n)-1); } };
SRM395 div1 medium
· 元記事
一番高いビルから入れていく。
一番左に入れると左から見えるビルが一つ増える。
一番右に入れると右から見えるビルが一つ増える。
それ以外の場所に入れても見えるビルは増えない。
#include <cstdio> #include <cstring> using namespace std; typedef long long LL; LL cache[100+10][100+10][100+10]; class Skyscrapers { public: int N; LL rec(int left,int right,int pos) { if(0<=left && 0<=right) { LL &r=cache[left][right][pos]; if(r!=-1) return r; else if(pos==N) r=(left==0 && right==0)?1:0; else if(pos==0) r=rec(left-1,right-1,pos+1); else r=(rec(left-1,right,pos+1)+(pos-1)*rec(left,right,pos+1)+rec(left,right-1,pos+1))%1000000007; return r; } else return 0; } int buildingCount(int _N, int leftSide, int rightSide) { N=_N; memset(cache,-1,sizeof(cache)); return rec(leftSide,rightSide,0); } };
SRM304 div1 medium
· 元記事
何個のサイコロがvを出すかの組み合わせで分ける
#include <algorithm> using namespace std; double cache[2500+10][50+10]; class Conditional { public: int v; int maxSide; double C[55][55]; double rec(int sum,int nD) { if(sum<=0) return 1.0; else if(nD==0) return 0.0; else { double &r=cache[sum][nD]; if(!(r<-1.0)) return r; r=0.0; for(int i=1;i<=maxSide;i++) if(i!=v) r+=rec(sum-i,nD-1)/(double)(maxSide-1); return r; } } double probability(int nDice, int _maxSide, int _v, int theSum) { v=_v; maxSide=_maxSide; for(int i=0;i<55;i++) for(int j=0;j<55;j++) C[i][j]=(i==0 || j==0)?1.0:C[i-1][j]+C[i][j-1]; double m=0,ans=0; fill(&cache[0][0],&cache[2500+10-1][50+10-1],-2.0); for(int i=1;i<=nDice;i++) m+=C[nDice-i][i]*pow(double(maxSide-1),double(nDice-i)); for(int i=1;i<=nDice;i++) ans+=C[nDice-i][i]*pow(double(maxSide-1),double(nDice-i))*rec(theSum-v*i,nDice-i); return ans/m; } };
SRM409 div1 medium
· 元記事
分からへんかった。
class MagicalSpheres { public: int count(int n,int p) { int r=0; while(n>1) { r+=n/p; n/=p; } return r; } bool check(int s,int f,int g) { for(int i=2;1<g;) { if(g%i==0) { int e=0; while(g%i==0) e++,g/=i; int c=count(s+f,i)-count(s,i)-count(f,i); if(c<e) return false; } i++; if(g<i*i) i=g; } return true; } int divideWork(int s,int f,int g) { for(int i=g;0<i;i--) if(check(s,f,i)) return i; } };
SRM391 div1 medium
· 元記事
ステージ1:
4番を爆破したとする。すると1番も開けることができる。

このような連鎖関係で箱を分ける。
このグループの数が爆弾の数より少ないならすべての箱を開けることができる。
そのようになる箱の分け方の場合の数を求めて(箱の数)!で割ればいい。
ステージ2:
”DP[箱の数][グループの数]=分け方”というDPを考える。
DP[1][1]=1は明らか
DP[i][j]は1:箱iが1つで1グループを形成する場合がDP[i-1][j-1]
2:箱iが他のグループに入り込む場合は(i-1)*DP[i-1][j]
(i-1個の箱ををj個のグループに分けたあと箱iを押し込む)
ステージ3:
規約分数にするために最大公約数で割る
#include <string> #include <sstream> typedef long long LL; class KeysInBoxes { public: LL DP[30][30]; LL gcd(LL a,LL b) { if(b==0) return a; else return gcd(b,a%b); } string getAllKeys(int N, int M) { DP[1][1]=1; for(int i=2;i<=N;i++) for(int j=1;j<=M;j++) DP[i][j]=DP[i-1][j-1]+(i-1)*DP[i-1][j]; LL c=0; LL m=1; for(int i=1;i<=M;i++) c+=DP[N][i]; for(int i=1;i<=N;i++) m*=i; LL g=gcd(c,m); c/=g,m/=g; stringstream ss; ss << c << "/" << m; return ss.str(); } };
SRM351 div1 medium
· 元記事
rec(直前の色、直前の色は何個続いたか、Aの個数、Bの個数、Cの個数)をメモ化再帰。
場所は全体の長さ-(Aの個数+Bの個数+Cの個数)で求められる。
#include <algorithm> #include <vector> using namespace std; int cache[3][4][50+10][50+10][50+10]; class BoxesArrangement { public: int len; string boxes; int rec(int w,int n,int A,int B,int C) { int p=len-A-B-C; int &r=cache[w][n][A][B][C]; if(r!=-1000) return r; else if(n==3) r=-100; else if(p==len) r=0; else { r=-100; if(0<A) { int nn=(w==0)?n+1:1; int t=(boxes[p]=='A')?1:0; r=max(r,rec(0,nn,A-1,B,C)+t); } if(0<B) { int nn=(w==1)?n+1:1; int t=(boxes[p]=='B')?1:0; r=max(r,rec(1,nn,A,B-1,C)+t); } if(0<C) { int nn=(w==2)?n+1:1; int t=(boxes[p]=='C')?1:0; r=max(r,rec(2,nn,A,B,C-1)+t); } } return r; } int maxUntouched(string _boxes) { boxes=_boxes; len=boxes.size(); int A=0,B=0,C=0; for(int i=0;i<boxes.size();i++) if(boxes[i]=='A') A++; else if(boxes[i]=='B') B++; else C++; cout << endl; fill(&cache[0][0][0][0][0],&cache[2][3][59][59][59],-1000); int ans=rec(0,0,A,B,C); return (ans<0)?-1:ans; } };
SRM484 div1 medium
· 元記事
解説見ました。
Login - TopCoder Wiki
class PuyoPuyo { public: int theCount(int L, int N) { memset(DP,0,sizeof(DP)); DP[0][0]=1; for(int i=0;i<N;i++) { DP[i+1][L-1]=(DP[i+1][L-1]+4*DP[i][0])%1000000007; for(int j=1;j<N;j++) { DP[i+1][j+L-1]=(DP[i+1][j+L-1]+3*DP[i][j])%1000000007; DP[i+1][j-1]=(DP[i+1][j-1]+DP[i][j])%1000000007; } } return DP[N][0]; } };
SRM393 div1 medium
· 元記事
無理やり感が否めない。
基本的にシンボルとパターンを総当りでぶつけていく。
でも、どの電球が生きているのかはパターンすべての中で一回でもついたことのある電球だけ。
パターンiとシンボルjをぶつけるときに、
パターン[i][k]=='1' && シンボル[j][k]=='0' または
電球kが生きている && パターン[i][k]=='0' && シンボル[j][k]
となるkがあったらその組み合わせはあり得ない。
#include <string> #include <vector> using namespace std; class NSegmentDisplay { public: string brokenSegments(vector <string> symbols, vector <string> patterns) { int n=symbols[0].size(); string ans(n,'?'); vector<string> cand; for(int i=0;i<patterns.size();i++) for(int j=0;j<n;j++) if(patterns[i][j]=='1') ans[j]='Y'; for(int pi=0;pi<patterns.size();pi++) { string p(n,'#'); for(int si=0;si<symbols.size();si++) { int i; for(i=0;i<n;i++) { if(patterns[pi][i]=='1' && symbols[si][i]=='0') break; else if(ans[i]=='Y' && symbols[si][i]=='1' && patterns[pi][i]=='0') break; } if(i==n) { if(p[0]=='#') for(i=0;i<n;i++) { if(patterns[pi][i]=='1' && symbols[si][i]=='1') p[i]='Y'; else if(patterns[pi][i]=='1' && symbols[si][i]=='0') ; else if(patterns[pi][i]=='0' && symbols[si][i]=='1') p[i]='N'; else if(patterns[pi][i]=='0' && symbols[si][i]=='0') p[i]='?'; else ; } else { for(i=0;i<n;i++) if(patterns[pi][i]=='1') ; else if(symbols[si][i]=='0') p[i]='?'; } } } if(p[0]=='#') return "INCONSISTENT"; cand.push_back(p); } for(int i=0;i<cand.size();i++) for(int j=0;j<n;j++) if(ans[j]=='?') { ans[j]=cand[i][j]; } else { if(ans[j]!=cand[i][j] && cand[i][j]!='?') return "INCONSISTENT"; } return ans; } };
SRM398 div1 medium
· 元記事
cache[y][x][何個訪れた][最後に訪れた番号]でメモ化再帰。
#include <cstring> #include <vector> #include <cstdio> using namespace std; int field[60][60]; int cache[60][60][60][60]; class CountPaths { public: int rec(int y,int x,int v,int l) { if(y<1 || x<1 || v<0) return 0; else { int &r=cache[y][x][v][l]; if(r!=-1) ; else if(y==1 && x==1) { if(field[y][x]!=-1 && v==1) r=(field[y][x]<l)?1:0; else if(field[y][x]==-1 && v==0) r=1; else r=0; } else if(field[y][x]!=-1 && field[y][x]<l) r=(rec(y-1,x,v-1,field[y][x])+rec(y,x-1,v-1,field[y][x]))%1000007; else if(field[y][x]==-1) r=(rec(y-1,x,v,l)+rec(y,x-1,v,l))%1000007; else r=0; return r; } } vector <int> difPaths(int r, int c, vector <int> fieldrow, vector <int> fieldcol) { memset(cache,-1,sizeof(cache)); for(int i=0;i<=r;i++) for(int j=0;j<=c;j++) field[i][j]=-1; for(int i=0;i<fieldrow.size();i++) field[fieldrow[i]][fieldcol[i]]=i; vector<int> ans; for(int i=0;i<=fieldrow.size();i++) ans.push_back(rec(r,c,i,51)); return ans; } };
SRM396 div1 medium
· 元記事
めんどくさい。
#include <queue> #include <string> #include <vector> using namespace std; class FixImage { public: int h,w; int image[60][60]; int color() { for(int i=0;i<h;i++) for(int j=0;j<w;j++) if(image[i][j]!=-1) image[i][j]=3000; int c=0; for(int sy=0;sy<h;sy++) for(int sx=0;sx<w;sx++) if(image[sy][sx]==3000) { queue<int> que; que.push(sy*60+sx); while(!que.empty()) { int y=que.front()/60,x=que.front()%60; que.pop(); if(image[y][x]==3000) { image[y][x]=c; if(y<h-1 && image[y+1][x]==3000) que.push((y+1)*60+x); if(0<y && image[y-1][x]==3000) que.push((y-1)*60+x); if(x<w-1 && image[y][x+1]==3000) que.push(y*60+x+1); if(0<w && image[y][x-1]==3000) que.push(y*60+x-1); } } c++; } return c; } void fill(int c) { for(int y=0;y<h;y++) { int left,right; for(left=0;left<w;left++) if(image[y][left]==c) break; for(right=w-1;0<=right;right--) if(image[y][right]==c) break; for(int x=left;x<=right;x++) image[y][x]=c; } for(int x=0;x<w;x++) { int bottom,ceil; for(bottom=0;bottom<h;bottom++) if(image[bottom][x]==c) break; for(ceil=h-1;0<=ceil;ceil--) if(image[ceil][x]==c) break; for(int y=bottom;y<=ceil;y++) image[y][x]=c; } } vector <string> originalImage(vector <string> alteredImage) { h=alteredImage.size(),w=alteredImage[0].size(); for(int i=0;i<h;i++) for(int j=0;j<w;j++) image[i][j]=(alteredImage[i][j]=='.')?-1:3000; for(int time=0;time<50;time++) { int n=color(); for(int i=0;i<=n;i++) fill(i); } for(int i=0;i<h;i++) for(int j=0;j<w;j++) alteredImage[i][j]=(image[i][j]==-1)?'.':'#'; return alteredImage; } };
SRM449 div1 medium
· 元記事
頑張った。long long をintに変えるとちょっと実行時間が短くなる。
#include <vector> #include <cstdio> #include <cstring> using namespace std; typedef long long LL; int offset=8; int cache[17+5][(1<<17)]; class HexagonalBattlefield { public: int N; int field[17+5][17+5]; int DFS(int x,int y) { LL r=0; if(x==17) { int m=0; for(int i=0;i<17;i++) if(field[i][y+1]=='#') m+=(1<<i); r=rec(y+1,m); } else if(field[x][y]=='#') r=DFS(x+1,y); else { if(x<16 && field[x+1][y]=='.') { field[x][y]=field[x+1][y]='#'; r=(r+DFS(x+1,y))%2000000011;; field[x][y]=field[x+1][y]='.'; } if(y<16 && field[x][y+1]=='.') { field[x][y]=field[x][y+1]='#'; r=(r+DFS(x+1,y))%2000000011;; field[x][y]=field[x][y+1]='.'; } if(x<16 && y<16 && field[x+1][y+1]=='.') { field[x][y]=field[x+1][y+1]='#'; r=(r+DFS(x+1,y))%2000000011;; field[x][y]=field[x+1][y+1]='.'; } } return (int)r%2000000011; } int rec(int y,int mask) { int &r=cache[y][mask]; if(r!=-1) ; else if(y==17) r=1; else r=DFS(0,y); return r; } int countArrangements(vector <int> X, vector <int> Y, int _N) { N=_N; for(int x=0;x<17;x++) for(int y=0;y<17;y++) field[x][y]='#'; for(int x=-N+1;x<=N-1;x++) { int b=(x<=0)?-N+1:-N+1+x; int c=(x<=0)?N-1+x:N-1; for(int y=b;y<=c;y++) field[x+offset][y+offset]='.'; } for(int i=0;i<X.size();i++) field[X[i]+offset][Y[i]+offset]='#'; int m=0; for(int i=0;i<17;i++) if(field[0][i]=='#') m+=(1<<i); memset(cache,-1,sizeof(cache)); return rec(0,m); } };
SRM431 div1 medium
· 元記事
なんかそんなに状態ないんじゃねと思ってメモ化した。
rng_58さんのコードを見るとAが10以上のときは0らしい。
#include <map> typedef long long LL; using namespace std; class MegaCoolNumbers { public: int N,A; map<LL,LL> cache; LL rec(int pos,int diff,int group,int before) { LL key=pos; key=key*20+diff; key=key*2000+group; key=key*20+before; if(pos==N) return (group==A)?1:0; else if(cache.find(key)!=cache.end()) return cache[key]; else { LL r=0; if(diff<10) for(int now=before;now<10;now++) if(now-before==diff) r=(r+rec(pos+1,diff,group,now))%1000000007; else r=(r+rec(pos+1,10,group+1,now))%1000000007; else for(int now=before;now<10;now++) r=(r+rec(pos+1,now-before,group,now))%1000000007; return cache[key]=r; } } int count(int _N, int _A) { N=_N,A=_A; LL ans=0; cache.clear(); for(int i=1;i<10;i++) ans=(ans+rec(1,10,1,i))%1000000007;; return ans; } };
SRM486 div1 medium
· 元記事
どっかでやったことがあるような気がする。
メモ化再帰。
#include <vector> #include <map> using namespace std; class QuickSort { public: map<vector<int>,double> cache; double rec(vector<int> L) { double r; if(L.size()<2) r=0.0; else if(cache.find(L)!=cache.end()) return cache[L]; else { r=0.0; int n=L.size(); for(int i=0;i<n;i++) { vector<int> left,right; for(int j=0;j<n;j++) { if(L[j]<L[i]) { left.push_back(L[j]); if(i<j) r+=1.0/(double)n; } if(L[i]<L[j]) { right.push_back(L[j]); if(j<i) r+=1.0/(double)n; } } r+=(rec(left)+rec(right))/(double)n; } } return cache[L]=r; } double getEval(vector <int> L) { return rec(L); } };
SRM425 div1 medium
· 元記事
しんどい。
やることはただのBFS。でも状態をmapでメモしとかないと無限ループに落ちる。
#include <string> #include <vector> #include <queue> #include <map> using namespace std; int mv[4][2]={{-1,0},{1,0},{0,-1},{0,1}}; class PiecesMover { public: int nP; bool check(vector<int> pos) { vector<string> board(5,string(5,'.')); for(int i=0;i<nP;i++) board[pos[i]/5][pos[i]%5]='*'; int connected=0; queue<int> que; que.push((pos[0]/5)*5+pos[0]%5); while(!que.empty()) { int y=que.front()/5,x=que.front()%5; que.pop(); if(board[y][x]!='*') continue; connected++; board[y][x]='#'; for(int i=0;i<4;i++) { int yy=y+mv[i][0],xx=x+mv[i][1]; if(yy<0 || 4<yy || xx<0 || 4<xx) continue; que.push(yy*5+xx); } } return (connected==nP); } int getMinimumMoves(vector <string> board) { nP=0; vector<int> first; for(int i=0;i<5;i++) for(int j=0;j<5;j++) if(board[i][j]=='*') first.push_back(i*5+j),nP++; map<vector<int>,int> used; queue<pair<vector<int>,int> > que; que.push(make_pair(first,0)); while(!que.empty()) { vector<int> v=que.front().first; int d=que.front().second; que.pop(); if(used.find(v)!=used.end()) continue; else if(check(v)) return d; else { used[v]=d; for(int i=0;i<nP;i++) for(int j=0;j<4;j++) { vector<int> vv=v; int y=vv[i]/5+mv[j][0],x=vv[i]%5+mv[j][1]; if(y<0 || 4<y || x<0 || 4<x) continue; vv[i]=y*5+x; int k; for(k=0;k<nP;k++) if(k!=i && vv[i]==vv[k]) break; if(k==nP) que.push(make_pair(vv,d+1)); } } } return -1; } };
SRM462 div1 medium
· 元記事
頭がDPでこちこちになってた。
#include <vector> using namespace std; class CandyBox { public: vector <double> expectedScore(int C, vector <int> score, int S) { int n=score.size(); vector<double> ans(score.begin(),score.end()); for(int time=0;time<S;time++) { vector<double> tmp=ans; for(int i=0;i<n;i++) for(int j=0;j<n;j++) tmp[i]+=(ans[j]-ans[i])*2/n/(n*C-1); ans=tmp; } return ans; } };
SRM319 div1 medium
· 元記事
ACRushさすが。
#include <set> #include <vector> typedef long long LL; using namespace std; class ConstructBST { public: int n; LL ans; LL C[30][30]; set<int> se; int rec(int pos) { if(se.find(pos)==se.end()) return 0; else { int left=rec(pos*2); int right=rec(pos*2+1); ans*=C[left][right]; return left+1+right; } } LL numInputs(vector <int> tree) { se.clear(); n=tree.size(); ans=1; for(int i=0;i<n;i++) se.insert(tree[i]); for(int i=0;i<30;i++) for(int j=0;j<30;j++) C[i][j]=(i==0 || j==0)?1:C[i-1][j]+C[i][j-1]; rec(1); return ans; } };
SRM372 div1 medium
· 元記事
お金が500しかないところがなんだかDP臭いのでDPだろうと思って考える。
だいたい出来るけど11の倍数っていう制約が厄介。
"11の倍数"で検索すると
a*1000+b*100+c*10+dという数(a,b,c,dは9以下)があったとき
(a-b+c-d)%11==0なら11の倍数らしい。
それ分かると普通にメモ化再帰できる。
#include <string> #include <vector> #include <sstream> #include <cstdio> #include <cstring> typedef long long LL; using namespace std; LL cache[32+10][11+10][500+10]; class RoundOfEleven { public: int n; string number; LL rec(int pos,int diff,int money) { LL &r=cache[pos][diff][money]; if(r!=-1) ; else if(pos==n) r=(diff==0)?money:0; else { r=0; for(int i=0;i<10;i++) { int m=i+'0'-number[pos]; m=(0<m)?m:-m; int d=(pos%2==0)?i:(11-i); if(0<money-m) r+=rec(pos+1,(diff+d)%11,money-m); } } return r; } long long maxIncome(int input, int money) { stringstream ss; ss<<input; ss>>number; n=number.size(); memset(cache,-1,sizeof(cache)); return rec(0,0,money); } };
SRM301 div1 medium
· 元記事
ワーシャルフロイドして一番長い2点間最短距離を返せばOK。
#include <vector> using namespace std; int chain[50+10][50+10]; class EscapingJail { public: int n; int getMaxDistance(vector <string> _chain) { n=_chain.size(); for(int i=0;i<n;i++) for(int j=0;j<n;j++) { char c=_chain[i][j]; if('0'<=c && c<='9') chain[i][j]=c-'0'; else if('a'<=c && c<='z') chain[i][j]=c-'a'+10; else if('A'<=c && c<='Z') chain[i][j]=c-'A'+36; else chain[i][j]=(1<<28); } for(int k=0;k<n;k++) for(int i=0;i<n;i++) for(int j=0;j<n;j++) chain[i][j]=min(chain[i][j],chain[i][k]+chain[k][j]); int ans=0; for(int i=0;i<n;i++) for(int j=0;j<n;j++) ans=max(ans,chain[i][j]); return ((1<<28)<=ans)?-1:ans; } };
SRM318 div1 medium
· 元記事
rec(0)=(rec(1)+cells[1]+......+rec(6)+cell[6])/6
rec(1)=(rec(2)+cells[2]+......+rec(7)+cell[7])/6
のような連立方程式を解けばいいのは分かったのだけど、そんなの分からない。
というわけで別の方法を考えた。
再帰の深さが1増えるたびに1/6されるから、
ある程度深くなったら誤差で落ちてしまうのではないか
とか思ってビビリながらsubmitしたら通ってしまった。
#include <vector> using namespace std; double cache[15+10][1000+10]; class CyclicGame { public: int n; vector<int> cells; double rec(int pos,int depth) { double &r=cache[pos][depth]; if(1000<depth) r=0.0; else if(r<1000000.0) ; else { r=0.0; for(int i=1;i<=6;i++) r+=(cells[(pos+i)%n]+rec((pos+i)%n,depth+1))/6.0; r=max(r,0.0); } return r; } double estimateBank(vector <int> _cells) { cells=_cells; n=cells.size(); for(int i=0;i<15+10;i++) for(int j=0;j<1000+10;j++) cache[i][j]=1000000.0+10.0; return rec(0,0); } };
SRM387 div1 medium
· 元記事
動物的直感でAC。
#include <algorithm> #include <cstdio> #include <cstring> #include <utility> #include <vector> using namespace std; class IntervalSubsets { public: int n; int cache[55]; vector<pair<int,int> > interval; int rec(int p) { int &r=cache[p]; if(r!=-1) return r; else { r=0; int ss=interval[p].second+1,se; for(int i=0;i<n;i++) if(ss<=interval[i].first) { se=interval[i].second; break; } for(int i=0;i<n;i++) if(ss<=interval[i].first && interval[i].first<=se) { r+=rec(i); se=min(se,interval[i].second); } return (r!=0)?r:r=1; } } int numberOfSubsets(vector <int> start, vector <int> finish) { interval.clear(); start.push_back(0); finish.push_back(0); n=start.size(); for(int i=0;i<n;i++) interval.push_back(make_pair(start[i],finish[i])); sort(interval.begin(),interval.end()); memset(cache,-1,sizeof(cache)); return rec(0); } };
SRM460 div1 medium
· 元記事
40^5でDP。
#include <vector> using namespace std; double DPJ[40+10][40+10][40*40+10]; double DPB[40+10][40+10][40*40+10]; class TheFansAndMeetingsDivOne { public: double find(vector <int> minJ, vector <int> maxJ, vector <int> minB, vector <int> maxB, int k) { int n=minJ.size(); for(int city=n;0<=city;city--) for(int chance=0;chance<=k;chance++) for(int left=0;left<=40*40;left++) { DPJ[city][chance][left]=0.0; if(chance==0 && left==0) DPJ[city][chance][left]=1.0; else if(city==n || chance==0) ; else { double h=(double)chance/(double)(n-city); double a=maxJ[city]-minJ[city]+1.0; for(int i=minJ[city];i<=maxJ[city] && 0<=left-i;i++) DPJ[city][chance][left]+=h*DPJ[city+1][chance-1][left-i]/a; DPJ[city][chance][left]+=(1-h)*DPJ[city+1][chance][left]; } DPB[city][chance][left]=0.0; if(chance==0 && left==0) DPB[city][chance][left]=1.0; else if(city==n || chance==0) ; else { double h=(double)chance/(double)(n-city); double a=maxB[city]-minB[city]+1.0; for(int i=minB[city];i<=maxB[city] && 0<=left-i;i++) DPB[city][chance][left]+=h*DPB[city+1][chance-1][left-i]/a; DPB[city][chance][left]+=(1-h)*DPB[city+1][chance][left]; } } double ans=0.0; for(int i=0;i<=40*40;i++) ans+=DPJ[0][k][i]*DPB[0][k][i]; return ans; } };
SRM459 div1 medium
· 元記事
一番下の列を決めたら全部決まる。
一番下の列の要素は{1,5,10,10,5,1}倍された和がtopに等しくなる。
これは二項係数。
じゃあナップサックできるじゃん。
ここでオーダーを下げきれずにTLE。
どの要素も正なのでbaseLengthがある程度大きくなったら弾いちゃってOK。
#include <vector> typedef long long LL; using namespace std; LL DP[1000000+10][2]; class NumberPyramids { public: int count(int baseLength, int top) { int C[21][21]; int bottom[20]; if(20<baseLength) return 0; for(int i=0;i<21;i++) for(int j=0;j<21;j++) C[i][j]=(i==0 || j==0)?1:C[i-1][j]+C[i][j-1]; for(int i=0;i<baseLength;i++) bottom[i]=C[baseLength-1-i][i],top-=bottom[i] if(top<0) return 0; for(int i=baseLength;0<=i;i--) for(int j=0;j<=top;j++) if(i==baseLength) DP[j][i%2]=(j==0)?1:0; else { DP[j][i%2]=DP[j][(i+1)%2]; if(0<=j-bottom[i]) DP[j][i%2]=(DP[j][i%2]+DP[j-bottom[i]][i%2])%1000000009; } return DP[top][0]; } };
SRM330 div1 medium
· 元記事
#include <algorithm> #include <string> #include <vector> typedef long long LL; using namespace std; class PrefixFreeSubsets { public: LL cantPrefFreeSubsets(vector <string> words) { int n=words.size(); LL ans[60]; ans[n]=1; sort(words.begin(),words.end()); for(int i=n-1;0<=i;i--) { int j=i+1; while(j<n && words[i].size()<=words[j].size() && words[i]==words[j].substr(0,words[i].size())) j++; ans[i]=ans[i+1]+ans[j]; } return ans[0]; } };
SRM315 div1 medium
· 元記事
テストケース見たら最大でも288通りしかないことがわかったので、深さ優先で全探索。
数字が埋まっていても数独としては成り立っていない場合を忘れ一回resubmit。
#include <string> #include <vector> using namespace std; class SillySudoku { public: vector<string> group; vector<string> board; int rec(int pos) { if(pos==16) return 1; else { int y=pos/4,x=pos%4,r=0; bool isEmpty=(board[y][x]=='-'); if(!isEmpty) { vector<bool> used(5,false); for(int i=0;i<4;i++) for(int j=0;j<4;j++) if(i==y || j==x || group[i][j]==group[y][x]) { if(i!=y || j!=x) { char c=board[i][j]; if(c!='-') used[c-'0']=true; } } if(used[board[y][x]-'0']) return 0; else return rec(pos+1); } else { vector<bool> used(5,false); for(int i=0;i<4;i++) for(int j=0;j<4;j++) if(i==y || j==x || group[i][j]==group[y][x]) { char c=board[i][j]; if(c!='-') used[c-'0']=true; } for(int cur=1;cur<5;cur++) if(!used[cur]) { board[y][x]=cur+'0'; r+=rec(pos+1); } board[y][x]='-'; return r; } } } int countWays(vector <string> _board) { board=_board; group.push_back("AABB"); group.push_back("AABB"); group.push_back("CCDD"); group.push_back("CCDD"); return rec(0); } };
SRM334 div1 medium
· 元記事
#define LIM 3500000
が大事。
3^6+9^6×6=3189375<3500000
なので範囲外アクセスを起こさない。
#include <cstring> #include <cstdio> #define LIM 3500000 typedef long long LL; using namespace std; LL cache[LIM]; class ExtendedHappyNumbers { public: int K; LL S(LL n) { LL r=0; while(n!=0) { LL a=n%10,b=1; for(LL i=0;i<K;i++) b*=a; r+=b; n/=10; } return r; } LL rec(LL n) { LL &r=cache[n]; if(0<r) ; else if(r==-3) r=LIM-1; else { r--; r=min(n,rec(S(n))); } return r; } LL calcTheSum(int A, int B, int _K) { K=_K; memset(cache,-1,sizeof(cache)); LL ans=0; for(LL i=1;i<LIM;i++) rec(i); cout << "calc-end" << endl; cout << "S(99)=" << S(99) << endl; for(LL i=A;i<=B;i++) ans+=cache[i]; return ans; } };
SRM302 div1 medium
· 元記事
ひどいソースだ。
#include <algorithm> #include <string> #include <vector> #include <iostream> #include <sstream> #include <cstdio> typedef long long LL; using namespace std; class IntegerPalindrome { public: LL findByIndex(int K) { LL t[30],u[30]; t[1]=t[2]=9; for(int i=3;i<30;i++) t[i]=t[i-2]*10; u[1]=0; for(int i=2;i<30;i++) u[i]=u[i-1]+t[i-1]; int len; for(len=1;len<30;len++) if(u[len]<=K && K<u[len+1]) break; K-=u[len]; printf("len=%d K=%d\n",len,K); stringstream ss; ss << K; string s1=ss.str(); printf("s1=%s\n",s1.c_str()); if(len%2==0) { if(s1.size()*2<len) s1=string(len/2-s1.size(),'0')+s1; s1[0]+=1; printf("s1=%s\n",s1.c_str()); string s2=s1; reverse(s1.begin(),s1.end()); s2+=s1; printf("s2=%s\n",s2.c_str()); stringstream tt(s2); LL l; tt>>l; return l; } else { if(s1.size()*2-1<len) s1=string(len/2-s1.size()+1,'0')+s1; s1[0]+=1; printf("s1=%s\n",s1.c_str()); string s2=s1; reverse(s1.begin(),s1.end()); s2+=s1.substr(1,s1.size()-1); printf("s2=%s\n",s2.c_str()); stringstream tt(s2); LL l; tt>>l; return l; } return 0; } };
SRM371 div1 medium
· 元記事
貪欲でOK。
usは小さい方からthemの大きい方と比較。
#include <algorithm> #include <vector> using namespace std; class ChessMatchup { public: int maximumScore(vector <int> us, vector <int> them) { int n=us.size(),ans=0; vector<int> usUsed(n,false); vector<int> themUsed(n,false); sort(us.begin(),us.end()); sort(them.begin(),them.end()); for(int i=0;i<n;i++) for(int j=n-1;0<=j;j--) if(!usUsed[i] && !themUsed[j] && us[i]>them[j]) themUsed[j]=true,usUsed[i]=true,ans+=2; for(int i=0;i<n;i++) for(int j=n-1;0<=j;j--) if(!usUsed[i] && !themUsed[j] && us[i]==them[j]) themUsed[j]=true,usUsed[i]=true,ans+=1; return ans; } };
SRM420 div1 medium
· 元記事
DP[r][b]=max(r-b,赤を引く確率*DP[r+1][b]+黒を引く確率*DP[r][b+1])でDP。
そのままメモリに取るとMLEするので、r+1またはb+1しかない性質を利用してメモリを節約。
#include <algorithm> using namespace std; class RedIsGood { public: double getProfit(int R, int B) { double DP[2][5010]; for(int i=0;i<=B+1;i++) DP[(R+1)%2][i]=0.0; for(int r=R;0<=r;r--) { for(int b=B;0<=b;b--) { double red=(double)(R-r)/(double)((R+B)-(r+b)); double a=red*DP[(r+1)%2][b]+(1.0-red)*DP[r%2][b+1]; DP[r%2][b]=max((double)r-b,a); } DP[r%2][B+1]=0.0; } return DP[0][0]; } };
あと84問
SRM498 div1 medium
· 元記事
map便利。
#include <algorithm> #include <string> #include <vector> #include <iostream> #include <sstream> #include <cstdio> #include <map> typedef long long LL; using namespace std; int ABS(int n) { return (0<n)?n:-n; } class FoxStones { public: int getCount(int N, int M, vector <int> sx, vector <int> sy) { map<vector<int>,LL> ma; for(int x=1;x<=N;x++) for(int y=1;y<=M;y++) { vector<int> v; for(int i=0;i<sx.size();i++) v.push_back(max(ABS(x-sx[i]),ABS(y-sy[i]))); if(ma.find(v)==ma.end()) ma[v]=1; else ma[v]++; } LL ans=1,MOD=1000000009; map<vector<int>,LL>::iterator itr=ma.begin(); for(itr;itr!=ma.end();itr++) { int l=(*itr).second; while(l!=0) ans=ans*l%MOD,l--; } return (int)ans; } };
SRM392 div1 medium
· 元記事
ループの中にあるかどうかを調べて、あとは数えるだけ。
#include <string> #include <vector> #include <iostream> #include <sstream> #include <algorithm> #include <cstdio> using namespace std; class RoundAboutCircle { public: int loopSize[200010]; int nextCell[200010]; int DFS(int k) { if(loopSize[k]!=-6) return loopSize[k]; else return loopSize[k]=DFS(nextCell[k])+1; } int maxScore(int N) { for(int i=1;i<=N;i++) { int s=0,t=i; while(t!=0) s+=t%10,t/=10; nextCell[i]=((i+s)%N==0)?N:(i+s)%N; } for(int i=1;i<=N;i++) loopSize[i]=0; for(int i=1;i<=N;i++) if(loopSize[i]==0) { int t=i; while(loopSize[t]==0) loopSize[t]=-2,t=nextCell[t]; if(loopSize[nextCell[t]]==-2) { int sz=0; while(loopSize[t]==-2) loopSize[t]=-4,t=nextCell[t],sz++; while(loopSize[t]==-4) loopSize[t]=sz,t=nextCell[t]; } t=i; while(loopSize[t]==-2) loopSize[t]=-6,t=nextCell[t]; } for(int i=1;i<=N;i++) if(loopSize[i]==-6) DFS(i); return *max_element(loopSize,loopSize+200010-1); } };
SRM503 div1 easy
· 元記事
#include <algorithm> #include <string> #include <vector> #include <iostream> #include <sstream> #include <cstdio> using namespace std; class ToastXToast { public: int bake(vector <int> uT, vector <int> oT) { int n=uT.size(),m=oT.size(),i,j,ans=0; vector<int> uG(n,-1); vector<int> oG(m,-1); for(i=0;i<n;i++) uT[i]*=2; for(i=0;i<m;i++) oT[i]*=2; sort(uT.begin(),uT.end()); sort(oT.begin(),oT.end()); for(i=0;i<m;i++) if(uT[n-1]+1<oT[i]) { oG[i]=0; ans++; break; } if(i==m) return -1; if(oG[0]==-1) { for(i=n-1;0<=i;i--) if(uT[i]<oT[0]-1) { ans++; break; } if(i==-1) return -1; } return ans; } };
SRM503 div1 medium
· 元記事
タイプミスで落とす。
#include <string> #include <vector> #include <iostream> #include <sstream> #include <cstdio> #include <cmath> #include <algorithm> using namespace std; class KingdomXCitiesandVillages { public: int n,m; double dist[100][100]; double determineLength(vector <int> cityX, vector <int> cityY, vector <int> villageX, vector <int> villageY) { n=cityX.size(),m=villageX.size(); for(int i=0;i<n+m;i++) for(int j=0;j<n+m;j++) { double xF=(i<n)?cityX[i]:villageX[i-n]; double yF=(i<n)?cityY[i]:villageY[i-n]; double xT=(j<n)?cityX[j]:villageX[j-n]; double yT=(j<n)?cityY[j]:villageY[j-n]; double xD=xT-xF,yD=yT-yF; dist[i][j]=sqrt(xD*xD+yD*yD); } double ans=0.0; for(int picked=n;picked<n+m;picked++) { double minToCity=dist[picked][0]; for(int i=0;i<n;i++) minToCity=min(minToCity,dist[picked][i]); vector<double> candiate; for(int i=n;i<n+m;i++) if(i!=picked && dist[picked][i]<minToCity) candiate.push_back(dist[picked][i]); candiate.push_back(minToCity); sort(candiate.begin(),candiate.end()); /*for(int i=0;i<(int)candiate.size();i++) printf("%2.2f ",candiate[i]); cout << endl;*/ double prob=1.0; double d=2.0; for(int i=0;0.0+1e-10<prob && i<(int)candiate.size();i++) { if(i!=candiate.size()-1) ans+=candiate[i]*prob*1.0/d; else ans+=candiate[i]*prob; prob-=prob*1.0/d; d+=1.0; } } return ans; } };
SRM364 div1 medium
· 元記事
いまどのプラントが稼働しているかを状態としてダイクストラ。
#include <algorithm> #include <queue> #include <string> #include <vector> using namespace std; int popCount(int _mask) { int r=0; for(int i=0;i<28;i++) if(_mask & (1<<i)) r++; return r; } class Q { public: int cost,state; bool operator<(const Q &q)const{ return q.cost<cost; } }; class PowerPlants { public: int dist[(1<<17)]; int minCost(vector <string> connectionCost, string plantList, int numPlants) { int n=plantList.size(),ans=(1<<28); fill(dist,&dist[(1<<17)-1],(1<<28)); Q q; priority_queue<Q> que; q.cost=0,q.state=0; for(int i=0;i<n;i++) if(plantList[i]=='Y') q.state+=(1<<i); que.push(q); while(!que.empty()) { int c=que.top().cost,s=que.top().state; que.pop(); /*for(int i=0;i<n;i++) if(s&(1<<i)) printf("Y"); else printf("N"); printf("=%d\n",c);*/ if(c<dist[s]) { dist[s]=c; if(popCount(s)<numPlants) { for(int i=0;i<n;i++) if(s&(1<<i)) for(int j=0;j<n;j++) if(!(s&(1<<j))) { q.state=s+(1<<j); char ch=connectionCost[i][j]; int add=('0'<=ch && ch<='9')?(ch-'0'):(ch-'A'+10); //printf("c=%d add=%d\n",c,add); q.cost=c+add; //printf("%d->%d i=%d j=%d\n",c,q.cost,i,j); que.push(q); } } else { //printf("ans=min(ans,c);\n"); ans=min(ans,c); } } } return ans; } };
SRM339 div1 medium
· 元記事
”DP[何ラウンド目][前に何ドルかけた(※)][今何ドル持っている]=確率”でDP。
※2の指数、前に勝った場合は10とした。
#include <cmath> class TestBettingStrategy { public: double DP[55][11][2010]; double winProbability(int initSum, int goalSum, int rounds, int _prob) { for(int i=0;i<55;i++) for(int j=0;j<11;j++) for(int k=0;k<2010;k++) DP[i][j][k]=0.0; double prob=(double)_prob/100.0,ans=0.0; DP[0][10][initSum]=1.0; for(int r=0;r<=rounds;r++) for(int b=0;b<11;b++) for(int now=0;now<2010;now++) if(goalSum<=now) ans+=DP[r][b][now],DP[r][b][now]=0.0; else if(r!=rounds) { int bet=(b==10)?1:pow(2,(b+1)); if(bet<=now) { DP[r+1][10][now+bet]+=DP[r][b][now]*prob; DP[r+1][(b==10)?0:b+1][now-bet]+=DP[r][b][now]*(1.0-prob); } } return ans; } };
SRM454 div1 medium
· 元記事
メモ化再帰。
数字の処理がややこしい+コピペミスで150点。
#include <algorithm> #include <cstring> using namespace std; typedef long long LL; class NumbersAndMatches { public: int len; int number[20]; int addMatch[10][10]; int moveMatch[10][10]; LL cache[20][300][200]; LL rec(int k,int leftMatch,int canMove) { LL &r=cache[k][leftMatch][canMove]; if(r!=-1) ; else if(k==len) return r=(leftMatch==0)?1:0; else { r=0; int now=number[k]; for(int i=0;i<10;i++) if(moveMatch[now][i]<=canMove) r+=rec(k+1,leftMatch+addMatch[now][i],canMove-moveMatch[now][i]); } return r; } LL differentNumbers(LL N, int canMove) { char numTable[3][100]={" _ _ _ _ _ _ _ _ ", "| | | _| _||_||_ |_ ||_||_|", "|_| _||_ _| | _||_| ||_| _|"}; for(int i=0;i<10;i++) for(int j=0;j<10;j++) { addMatch[i][j]=0; moveMatch[i][j]=0; for(int y=0;y<3;y++) for(int x=0;x<3;x++) { if(numTable[y][x+i*3]!=' ') addMatch[i][j]++; if(numTable[y][x+j*3]!=' ') addMatch[i][j]--; if(numTable[y][x+i*3]!=' ' && numTable[y][x+j*3]==' ') moveMatch[i][j]++; } } len=0; for(int i=0;N!=0;i++) { len++; number[i]=N%10; N/=10; } memset(cache,-1,sizeof(cache)); return rec(0,0,canMove); } };
無題
· 元記事
開幕しましたねー。
新人の秋山っていう人が期待できるらしい。
SRM311 div1 medium
· 元記事
桁ごとに考えて足すだけ。
typedef long long LL; LL S(LL num) { num++; LL r=0; for(LL digit=0,i=10;digit<15;digit++,i*=10) { LL l=num; r+=l/i*45*(i/10); l%=i; for(LL j=0;0<l;j++,l-=(i/10)) r+=((i/10)<=l)?j*(i/10):j*l; } return r; } class SumThemAll { public: long long getSum(int lowerBound, int upperBound) { return S((LL)upperBound)-S((LL)(lowerBound-1)); } };
SRM412 div1 medium
· 元記事
実装。
”starting with the note that has the highest pitch , then the note with the
second -highest pitch , and so on .”
この文を見逃したので時間がかかった。
#include <algorithm> #include <iostream> #include <string> #include <vector> using namespace std; class StringsAndTabs { public: vector <string> transformTab(vector <string> tab, vector <int> stringsA, vector <int> stringsB, int d) { int AsN=stringsA.size(),tLen=tab[0].size(),BsN=stringsB.size(); vector<string> ans(BsN,string(tLen,'-')); for(int time=0;time<tLen;time++) { vector<int> pitch; for(int i=0;i<AsN;i++) { char c=tab[i][time]; if(c!='-') { int a=stringsA[i]+(('0'<=c && c<='9')?c-'0':c-'A'+10)+d; pitch.push_back(a); } } sort(pitch.begin(),pitch.end()); reverse(pitch.begin(),pitch.end()); bool b=true; for(int i=0;i<pitch.size();i++) { int minOpen=50; int st=-1; for(int j=BsN-1;0<=j;j--) if(ans[j][time]=='-') { int o=pitch[i]-stringsB[j]; if(0<=o && o<=35 && o<minOpen) minOpen=o,st=j; } if(st==-1) { b=false; break; } char c=(minOpen<10)?minOpen+'0':minOpen-10+'A'; ans[st][time]=c; } if(!b) for(int i=0;i<BsN;i++) ans[i][time]='x'; } return ans; } };
あと90問
SRM349 div1 medium
· 元記事
昇順にソートすると簡単になる。
rec(今何番目のサイコロ、最低でも出さなければいけない目)をメモ化。
#include <algorithm> #include <cstring> #include <vector> typedef long long LL; using namespace std; class DiceGames { public: int n; LL cache[50][50]; vector<int> sides; LL rec(int k,int least) { LL &r=cache[k][least]; if(r!=-1) ; else if(k==n) r=1; else { r=0; for(int i=least;i<=sides[k];i++) r+=rec(k+1,i); } return r; } LL countFormations(vector<int> _sides) { sides=_sides; n=sides.size(); sort(sides.begin(),sides.end()); memset(cache,-1,sizeof(cache)); return rec(0,1); } };
SRM338 div1 medium
· 元記事
最初のarray[a]=xとする
xがarray[a]にあるか、それ以外にあるかで確率を保持する。
一度swapするたびに
1:array[a]==xのとき
xが選ばれない確率は(nC2-n+1)/nC2
xが選ばれる確率は(n-1)/nC2
2:array[a]!=xのとき
xがarray[a]に移動する確率は1/nC2
xがarray[a]に移動しない確率は(nC2-1)/nC2
#include <cstdio> using namespace std; class RandomSwaps { public: double getProbability(int arrayLength, int swapCount, int a, int b) { double inA=1.0,inOther=0.0,n=arrayLength; double nC2=n*(n-1)/2; for(int i=0;i<swapCount;i++) { double a,o; a=inA*(nC2-(n-1))/nC2+inOther*1.0/nC2; o=inA*(n-1)/nC2+inOther*(nC2-1.0)/nC2; inA=a,inOther=o; } if(a==b) return inA; else return inOther/(n-1); } };
SRM467 div1 medium
· 元記事
ここより先に見たほうがいいところ:
SRM467 - cafelier@SRM - TopCoder部
Login - TopCoder Wiki
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
解けなかった。

だからSuperSum(n,k)は

で表を変形して

こうなる。
だから答えは
n+kCk+1
あとMODのライブラリはcafelierさんを丸パクリ参考にさせていただきました。
typedef long long LL; static const MOD=1000000007; LL ADD(LL x,LL y){ return (x+y)%MOD; } LL SUB(LL x,LL Y){ return (x-y+MOD)%MOD; } LL MUL(LL x,LL y){ return ((x*y)%MOD+MOD)%MOD; } LL POW(LL x,LL e) { LL r=1; for(;e;x=MUL(x,x);e>>=1) if(e&1) r=MUL(r,x); return r; } LL DIV(LL x,LL y){ return MUL(x,POW(y,MOD-2)); } LL COMB(LL n,LL k) { LL r=1; for(LL i=1;i<=k;i++) r=DIV(MUL(r,n-i+1),i); return r; } class SuperSum { public: int calculate(int k, int n) { return (int)COMB(n+k,k+1); } };
SRM320 div1 medium
· 元記事
メモ化再帰するだけ。
#include <sstream> #include <vector> using namespace std; class ContestSchedule { public: int n; double cache[55]; vector<int> start; vector<int> end; vector<double> winExp; double rec(int lastContest) { double &r=cache[lastContest]; if(!(r<0)) return r; r=0.0; for(int i=0;i<n;i++) if(end[lastContest]<=start[i]) r=max(r,winExp[i]+rec(i)); return r; } double expectedWinnings(vector<string> contests) { contests.push_back("-1 0 0"); n=contests.size(); for(int i=0;i<n;i++) { cache[i]=-1.0; stringstream ss(contests[i]); int s,e; double w; ss >> s >> e >> w; w/=100.0; start.push_back(s); end.push_back(e); winExp.push_back(w); } return rec(n-1); } };
SRM466 div1 medium
· 元記事
class LotteryPyaterochka{ public: double chanceToWin(int N) { double n=N; return (10.0*(5*n-5)*(5*n-6)/2+5*(5*n-5)+1.0)*n*120/((5*n)*(5*n-1)*(5*n-2)*(5*n-3)*(5*n-4)); } };
SRM374 div1 medium
· 元記事
axis-aligned box that contains all the object's squares

#include <algorithm> #include <sstream> #include <string> #include <stack> #include <utility> #include <vector> #include <iostream> using namespace std; class PlayerExtraction { public: int bottom,ceil,left,right,S,h,w,k; vector<string> photo; void show() { for(int i=0;i<h;i++) cout << photo[i] << endl; cout << endl; } void calc(int startY,int startX) { bottom=(1<<28),ceil=-1,left=(1<<28),right=-1,S=0; int mv[4][2]={{0,-1},{0,1},{-1,0},{1,0}}; stack<pair<int,int> > sta; sta.push(make_pair(startY,startX)); while(!sta.empty()) { int y=sta.top().first,x=sta.top().second; sta.pop(); if(photo[y][x]!='0'+k) continue; bottom=min(bottom,y),ceil=max(ceil,y); left=min(left,x),right=max(right,x); S++; photo[y][x]='s'; for(int i=0;i<4;i++) { int yy=y+mv[i][0],xx=x+mv[i][1]; if(0<=yy && yy<h && 0<=xx && xx<w) sta.push(make_pair(yy,xx)); } } return; } vector<string> extractPlayers(vector<string> _photo,int _k,int threshold) { photo=_photo; h=photo.size(),w=photo[0].size(),k=_k; vector<pair<int,int> > tmp; for(int y=0;y<h;y++) for(int x=0;x<w;x++) { if(photo[y][x]=='0'+k) { calc(y,x); show(); if(threshold<=S*4) { ceil++,right++; tmp.push_back(make_pair(left+right,bottom+ceil)); } } } sort(tmp.begin(),tmp.end()); vector<string> ans; for(int i=0;i<(int)tmp.size();i++) { stringstream ss; ss << tmp[i].first << " " << tmp[i].second; ans.push_back(ss.str()); } return ans; } };
震災
· 元記事
先生の家族が被災されたそうです。
SRM308 div1 medium
· 元記事
最短距離=BFS
#include <algorithm> #include <cstring> #include <map> #include <queue> #include <string> #include <vector> #include <iostream> using namespace std; class CornersGame { public: int cache[36][36][36][36]; int countMoves(vector<string> board) { memset(cache,-1,sizeof(cache)); int mv[4][2]={{0,-1},{0,1},{-1,0},{1,0}}; int jp[4][2]={{0,-2},{0,2},{-2,0},{2,0}}; vector<int> firstState; firstState.push_back(28); firstState.push_back(29); firstState.push_back(34); firstState.push_back(35); queue<pair<vector<int>,int> > que; que.push(make_pair(firstState,0)); while(!que.empty()) { int dist=que.front().second; vector<int> state=que.front().first; que.pop(); sort(state.begin(),state.end()); int &r=cache[state[0]][state[1]][state[2]][state[3]]; if(r!=-1 && r<=dist) continue; r=dist; vector<string> b=board; for(int i=0;i<4;i++) b[state[i]/6][state[i]%6]='s'; for(int i=0;i<4;i++) for(int j=0;j<4;j++) { int yy=state[i]/6+mv[j][0]; int xx=state[i]%6+mv[j][1]; if(0<=yy && yy<6 && 0<=xx && xx<6 && b[yy][xx]=='.') { vector<int> v=state; v[i]=yy*6+xx; que.push(make_pair(v,dist+1)); } int yj=state[i]/6+jp[j][0]; int xj=state[i]%6+jp[j][1]; if(0<=yy && yy<6 && 0<=xx && xx<6 && b[yy][xx]=='s' && 0<=yj && yj<6 && 0<=xj && xj<6 && b[yj][xj]=='.') { vector<int> v=state; v[i]=yj*6+xj; que.push(make_pair(v,dist+1)); } } } return cache[0][1][6][7]; } };
後98問
SRM457 div1 medium
· 元記事
0:何種類の数字を使うか決める
1:二個つかう数字の余りを決める(組み合わせを求める)
2:一個使う数字の余りを決める(同上)
3:一個使う方は大きい方を使うか小さい方を使うかがある
#include <algorithm> #include <cstring> typedef long long LL; using namespace std; class TheHexagonsDivOne { public: int board[7]; LL cache[200][200]; LL C(int n,int r) { if(n<r) return 0; if(cache[n][r]!=-1) return cache[n][r]; if(r==0 || n==r) return cache[n][r]=1; return cache[n][r]=C(n-1,r)+C(n-1,r-1); } LL calc() { LL r=0; int o[7]; for(int i=0;i<7;i++) o[i]=i; do { int bo[7]; for(int i=0;i<7;i++) bo[i]=board[o[i]]; bool b=true; for(int i=1;i<5 && b;i++) b=(b && bo[i-1]!=bo[i]); b=(b && bo[0]!=bo[5] && bo[4]!=bo[5]); for(int i=0;i<6 && b;i++) b=(b && bo[i]!=bo[6]); if(b) r++; }while(next_permutation(o,o+7)); return r; } LL count(int n) { memset(cache,-1,sizeof(cache)); LL ans=0; board[0]=0,board[1]=1,board[2]=2,board[3]=3,board[4]=4,board[5]=5,board[6]=6; printf("same=0 calc()=%lld\n",calc()/6); ans+=C(n,0)*C(n,7)*(LL)(1<<7)*calc()/6; board[0]=0,board[1]=0,board[2]=2,board[3]=3,board[4]=4,board[5]=5,board[6]=6; printf("same=1 calc()=%lld\n",calc()/6); ans+=C(n,1)*C((n-1),5)*(LL)(1<<5)*calc()/6; board[0]=0,board[1]=0,board[2]=2,board[3]=2,board[4]=4,board[5]=5,board[6]=6; printf("same=2 calc()=%lld\n",calc()/6); ans+=C(n,2)*C((n-2),3)*(LL)(1<<3)*calc()/6; board[0]=0,board[1]=0,board[2]=2,board[3]=2,board[4]=4,board[5]=4,board[6]=6; printf("same=3 calc()=%lld\n",calc()/6); ans+=C(n,3)*C((n-3),1)*(LL)(1<<1)*calc()/6; return ans; } };
残り99問
div1mediumスタート
· 元記事
また長い道のりが始まった
SRM418 div2 hard
· 元記事
メモ化再帰すればいい。
だけど、
rec(m,b,o)でm=oかつm=unitsPerRoundな状態になると無限ループするので、途中で中断するように
if(r==-3) return r=(1<<28); r--;
を入れておいた。
#include <algorithm> #include <cstring> using namespace std; class BarracksEasy { public: int unitsPerRound; int cache[50+1][50+1][100]; int rec(int myUnits,int barHp,int opponentsUnits) { //printf("myUnits=%d barHp=%d opponentsUnits=%d\n",myUnits,barHp,opponentsUnits); if(barHp==0 && opponentsUnits==0) return 0; if(myUnits==0) return (1<<28); int &r=cache[myUnits][barHp][opponentsUnits]; if(0<r) return r; if(r==-3) return r=(1<<28); r--; int tmp=(1<<28); for(int killer=0;killer<=myUnits;killer++) { int breaker=myUnits-killer; int leftOpponentsUnits=max(0,opponentsUnits-killer); int leftOpponentHp=max(0,barHp-breaker); int leftMyUnits=max(0,myUnits-leftOpponentsUnits); leftOpponentsUnits+=(leftOpponentHp!=0)?unitsPerRound:0; tmp=min(tmp,1+rec(leftMyUnits,leftOpponentHp,leftOpponentsUnits)); } return r=tmp; } int attack(int myUnits, int barHp, int _unitsPerRound) { unitsPerRound=_unitsPerRound; memset(cache,0,sizeof(cache)); int ans=rec(myUnits,barHp,0); return ((1<<28)<=ans)?-1:ans; } };
これでdiv2hard100耐は終了した
SRM442 div2 hard
· 元記事
差を保存するところまでは思いついたのに、残念。
#include <cstring> #include <vector> #define TOWER_MAX 250000 using namespace std; class EqualTowers { public: int n; vector<int> bricks; int cache[TOWER_MAX+1][50]; int rec(int dif,int k) { if((k==n && dif!=0) || TOWER_MAX<dif) return -(1<<28); if(k==n && dif==0) return 0; if(cache[dif][k]!=-1) return cache[dif][k]; int a=rec(dif+bricks[k],k+1); int b=rec(dif,k+1); int c=(dif<bricks[k])?rec(bricks[k]-dif,k+1)+dif:rec(dif-bricks[k],k+1)+bricks[k]; return cache[dif][k]=max(a,max(b,c)); } int height(vector <int> _bricks) { bricks=_bricks; n=bricks.size(); memset(cache,-1,sizeof(cache)); int ans=rec(0,0); return (ans<=0)?-1:ans; } };
SRM343 div2 hard
· 元記事
全探索で大丈夫だけど、
かなりTLEしやすいので引数をグローバルにしたりして定数項を落とさないといけない。
メモ化したほうがいい。
#include <string> #include <vector> #include <iostream> using namespace std; class Mafia { public: int n,mine; int responses[16][16]; vector<int> guilt; vector<int> survivers; int rec() { int left=0; for(int i=0;i<n;i++) if(survivers[i]) left++; if(!survivers[mine] || left<2) return 0; if(left%2==1) { int maxG=-(1<<28),rI=0; for(int i=0;i<n;i++) if(survivers[i] && maxG<guilt[i]) maxG=guilt[i],rI=i; survivers[rI]=false; int r=rec(); survivers[rI]=true; return r; } else { int r=0; for(int i=0;i<n;i++) if(mine!=i && survivers[i]) { vector<int> g=guilt; for(int j=0;j<n;j++) guilt[j]+=responses[i][j]; survivers[i]=false; r=max(r,rec()+1); survivers[i]=true; guilt=g; } return r; } } int play(vector <int> _guilt, vector <string> _responses, int playerIndex) { mine=playerIndex; n=_guilt.size(); for(int i=0;i<n;i++) for(int j=0;j<n;j++) { char c=_responses[i][j]; responses[i][j]=('a'<=c && c<='z')?-(c-'a'+1):(c-'A'+1); } vector<int> v(n,true); guilt=_guilt; survivers=v; return rec(); } };
SRM441 div2 hard
· 元記事
分からない上に、公式の解説がないので諦める。
SRM400 div2 hard
· 元記事
DPではない。
最初の行と最初の列だけビットで全探索すればあとは左上を見るだけで全部決められる。
#include <string> #include <vector> using namespace std; class LightedPanels { public: int h,w; vector<string> board,tmp; int popCount(int _mask) { int r=0; for(int i=0;i<28;i++) if(_mask & (1<<i)) r++; return r; } void toggle(int y,int x) { int mv[9][2]={{-1,-1},{-1,0},{-1,1},{0,-1},{0,0},{0,1},{1,-1},{1,0},{1,1}}; for(int i=0;i<9;i++) { int yy=y+mv[i][0],xx=x+mv[i][1]; if(0<=yy && yy<h && 0<=xx && xx<w) { char c=board[yy][xx]; board[yy][xx]=(c=='.')?'*':'.'; } } return; } int minTouch(vector<string> _board) { board=_board; h=board.size(),w=board[0].size(); int ans=(1<<28); for(int row0=0;row0<(1<<w);row0++) for(int column0=0;column0<(1<<h);column0++) { int r=popCount(row0)+popCount(column0); tmp=board; for(int x=0;x<w;x++) if(row0 & (1<<x)) toggle(0,x); for(int y=0;y<h;y++) if(column0 & (1<<y)) toggle(y,0); for(int y=1;y<h;y++) for(int x=1;x<w;x++) if(board[y-1][x-1]=='.') toggle(y,x),r++; bool b=true; for(int y=0;y<h;y++) for(int x=0;x<w;x++) if(board[y][x]=='.') b=false; if(b) ans=min(ans,r); board=tmp; } return (ans==(1<<28))?-1:ans; } };
SRM319 div2 hard
· 元記事
5時間かかったorz
#include <sstream> #include <string> #include <vector> using namespace std; class IncompleteBST { public: char tree[(1<<20)]; bool rec(int cur,char bottom,char ceil) { if((1<<20)<=cur || tree[cur]=='#') return true; if(tree[cur]<bottom || ceil<tree[cur]) return false; return rec(cur*2,bottom,min(ceil,tree[cur])) && rec(cur*2+1,max((char)(tree[cur]+1),bottom),ceil); } string missingValues(vector<string> _tree) { int q=0; for(int i=0;i<(1<<20);i++) tree[i]='#'; for(int i=0;i<(int)_tree.size();i++) { stringstream ss(_tree[i]); char d; int n; ss >> d >> n; tree[n]=d; if(d=='?') q=n; } string ans; for(char c='A';c<='Z';c++) { tree[q]=c; if(rec(1,'A','Z')) ans+=c; } return ans; } };
SRM495 div2 hard
· 元記事
意味わかんない。階乗/2…why。
Login - TopCoder Wiki
↑に解説が書いてあるのだろうけどもう気力がマイナス。
分かったから解説してみる。
マスが5個の場合:

こんなふうに互いに交換可能なマスを一列に並べて番号をつける。
4番のところと0、1,2,3は交換できる。
だから↓

3番のところと0、1,2は交換できる。だから↓

要素が3つになった、これは3通り。
よって5×4×3=60通り
マスがn個の場合も同様に
n×(n-1)×・・・・・×3=n!÷2
#include <cstring> #include <string> #include <vector> typedef long long LL; using namespace std; class UNIONFIND { public: vector<int> date; vector<int> scale; UNIONFIND(int n) { for(int i=0;i<n;i++) { date.push_back(i); scale.push_back(1); } } int root(int n) { if(date[n]==n) return n; else return date[n]=root(date[n]); } void unionSet(int x,int y) { if(root(x)==root(y)) return; scale[root(y)]+=scale[root(x)]; scale[root(x)]=0; date[root(x)]=date[root(y)]; } int unionSize(int n) { return scale[root(n)]; } }; class HexagonPuzzle { public: LL MOD; int theCount(vector<string> board) { MOD=1000000007; int n=board.size(); UNIONFIND U(n*n); for(int y1=0;y1<n;y1++) for(int x1=0;x1<=y1;x1++) { int y2=y1+1,y3=y1+1,x2=x1,x3=x1+1; if(0<=y2 && y2<n && 0<=x2 && x2<=y2 && 0<=y3 && y3<n && 0<=x3 && x3<=y3 && board[y1][x1]=='.' && board[y2][x2]=='.' && board[y3][x3]=='.') U.unionSet(y1*n+x1,y2*n+x2),U.unionSet(y1*n+x1,y3*n+x3); y2=y1,y3=y1+1,x2=x1+1,x3=x1+1; if(0<=y2 && y2<n && 0<=x2 && x2<=y2 && 0<=y3 && y3<n && 0<=x3 && x3<=y3 && board[y1][x1]=='.' && board[y2][x2]=='.' && board[y3][x3]=='.') U.unionSet(y1*n+x1,y2*n+x2),U.unionSet(y1*n+x1,y3*n+x3); } LL ans=1; LL fact[3000]; fact[2]=1; for(LL i=3;i<3000;i++) fact[i]=fact[i-1]*i%MOD; //printf("here\n"); for(int i=0;i<n*n;i++) { LL a=0; for(int j=0;j<n*n;j++) if(U.root(j)==i) a++; //printf("a=%d\n",a); if(2<a) ans=(ans*fact[a])%MOD; } return (int)ans; } };
シェルスクリプト作ってみた
· 元記事
下手ですみません
#!/bin/bash #solved_probrem.sh for f in `find . -name "*.cpp" | grep SRM` do dir=`echo $f | sed -e 's:.*SRM\(.*\)/\(.*\)[.]cpp:\1-\2:'` cla=`cat $f | grep class | sed -e 's:class *\([[:alpha:]]*\)[ {]*:\1:' | sed -n '$p'` if [ $cla ] then echo $dir-$cla fi done
SRM422 div2 hard
· 元記事
どの人が食べられるケーキにするかをビットで管理して全探索する。
#include <algorithm> #include <set> #include <sstream> #include <string> #include <vector> using namespace std; class BirthdayCake { public: set<string> fruit; int s2i(string s) { return (int)distance(fruit.begin(),fruit.find(s)); } int popCount(int n) { int r=0; for(int i=0;i<25;i++) if(n & (1<<i)) r++; return r; } int howManyFriends(vector <string> availableFruits, vector <string> friendsDislikings, int K) { int nFriend=friendsDislikings.size(); for(int i=0;i<(int)availableFruits.size();i++) fruit.insert(availableFruits[i]); for(int i=0;i<nFriend;i++) { string s; stringstream ss(friendsDislikings[i]); while(ss >> s) fruit.insert(s); } int nFruit=fruit.size(); vector<int> useFruit(nFruit,0); for(int i=0;i<(int)availableFruits.size();i++) useFruit[s2i(availableFruits[i])]=1; vector<vector<int> > fD(nFriend); for(int i=0;i<nFriend;i++) { string s; stringstream ss(friendsDislikings[i]); while(ss >> s) fD[i].push_back(s2i(s)); } int ans=0; int a=0; for(int i=0;i<nFruit;i++) a+=useFruit[i]; //printf("nFriend=%d nFruit=%d\n",nFriend,nFruit); for(int eat=0;eat<(1<<nFriend);eat++) { //printf("eat=%d\n",eat); int r=a; vector<int> v=useFruit; for(int i=0;i<nFriend;i++) if(eat & (1<<i)) { //printf("%d ",i); for(int j=0;j<fD[i].size();j++) if(v[fD[i][j]]==1) { r--; v[fD[i][j]]=0; //if(r<K) // goto B1; } } //B1: //printf("\n"); //printf("r=%d\n\n",r); if(K<=r) ans=max(ans,popCount(eat)); } return ans; } };
SRM419 div2 hard
· 元記事
無向グラフだからUNIONFINDで連結成分を分解できる。
後、テストケース3の
{"1 2,2 3,3 4,4 5,5 3,1 3,6 7,7 8,6 8,8 9,9 1",←ここと
ここが→ "0,10 11,11 9,12 13,14 15,15 16,16 17,14 17,14 16"}
つながって10になる
#include <cstring> #include <sstream> #include <string> #include <vector> using namespace std; class UNIONFIND { vector<int> date; vector<int> scale; public: UNIONFIND(int n) { for(int i=0;i<n;i++) { date.push_back(i); scale.push_back(1); } } int root(int n) { if(date[n]==n) return n; else return date[n]=root(date[n]); } void unionSet(int x,int y) { if(root(x)==root(y)) return; scale[root(y)]+=scale[root(x)]; scale[root(x)]=0; date[root(x)]=date[root(y)]; } int unionSize(int n) { return scale[root(n)]; } }; class CactusCount { public: int visited[200]; vector<int> edge[200]; bool DFS(int pre,int now) { //printf("pre=%d now=%d\n",pre,now); visited[now]++; if(visited[now]==3) return false; if(visited[now]==2) return true; for(int i=0;i<(int)edge[now].size();i++) if(edge[now][i]!=pre && !DFS(now,edge[now][i])) return false; return true; } int countCacti(int n, vector <string> _edges) { UNIONFIND U(n); string input; for(int i=0;i<(int)_edges.size();i++) input+=_edges[i]; for(int i=0;i<(int)input.size();i++) if(input[i]==',') input[i]=' '; stringstream ss(input); int cactus1,cactus2; while(ss >> cactus1 >> cactus2) { cactus1--,cactus2--; edge[cactus1].push_back(cactus2); edge[cactus2].push_back(cactus1); U.unionSet(cactus1,cactus2); } vector<int> OK(n,-2); for(int i=0;i<n;i++) if(OK[U.root(i)]==-2) { //printf("U.root(i)==%d\n",U.root(i)); for(int j=0;j<n;j++) if(U.root(j)==U.root(i) && OK[U.root(i)]==-2) { memset(visited,0,sizeof(visited)); if(!DFS(-1,j)) OK[U.root(i)]=-1; } if(OK[U.root(i)]==-2) OK[U.root(i)]=0; } int ans=0; for(int i=0;i<n;i++) if(OK[i]==0) ans++; return ans; } };
[[]]
ubuntu+gVimでgVimからクリップボードにコピー
· 元記事
「"+y」ーーーーー*じゃないって:h clipboardに書いてあった
SRM336 div2 hard
· 元記事
図を書いて1つ1つ考えれば難しくない。
…3回くらいresubmitしたけどね。
#include <algorithm> #include <vector> using namespace std; class MostLikely { public: int likelyRank(vector <int> sc, int low, int high) { vector<int> rank(55,0); sc.push_back(1000000000+1); sc.push_back(-1); sort(sc.begin(),sc.end()); reverse(sc.begin(),sc.end()); for(int i=1;i<(int)sc.size();i++) if(sc[i-1]==sc[i]) ; else if(sc[i]<low && high<sc[i-1]) rank[i]=high-low+1; else if(sc[i]<low) rank[i]=sc[i-1]-low; else if(high<sc[i-1]) rank[i]=high-sc[i]+1; else rank[i]=sc[i-1]-sc[i]; int m=*max_element(rank.begin(),rank.end()); if(1<(int)count(rank.begin(),rank.end(),m)) return -1; for(int i=0;i<55;i++) if(rank[i]==m) return i; return -2; } };
SRM499 div2 hard
· 元記事
点数が高い順にソートして貪欲に取っていけばOK。
pair使うのが面倒だったのでバブルソートしてみた。
#include <algorithm> #include <string> #include <vector> using namespace std; class PalindromeGame { public: int getMaximum(vector <string> front, vector <int> back) { int n=front.size(),ans=0; vector<int> used(n,false); for(int i=0;i<n;i++) for(int j=0;j<n-1;j++) if(back[j]<back[j+1]) swap(front[j],front[j+1]),swap(back[j],back[j+1]); for(int i=0;i<n;i++) for(int j=i+1;j<n;j++) if(!used[i] && !used[j]) { string s=front[i]; reverse(s.begin(),s.end()); if(s==front[j]) used[i]=used[j]=true,ans+=back[i]+back[j]; } for(int i=0;i<n;i++) if(!used[i]) { string s=front[i]; reverse(s.begin(),s.end()); if(s==front[i]) { used[i]=true,ans+=back[i]; break; } } return ans; } };
SRM334 div2 hard
· 元記事
難しい。分からん。
if(happiness[now]==-3) return now;
#include <algorithm> #include <cstring> #define LIMIT 5000000 typedef long long LL; using namespace std; class ExtendedHappyNumbers { public: int K; int happiness[LIMIT]; int rec(int now) { if(0<happiness[now]) return happiness[now]; happiness[now]--; if(happiness[now]==-3) return now; int n=0; int t=now; while(t!=0) { int s=1; for(int j=0;j<K;j++) s*=(t%10); n+=s; t/=10; } happiness[now]=min(now,rec(n)); return happiness[now]; } LL calcTheSum(int A, int B, int _K) { K=_K; memset(happiness,0,sizeof(happiness)); LL ans=0; for(LL i=A;i<=B;i++) ans+=(LL)rec(i); return ans; } };
SRM381 div2 hard
· 元記事
ソース31行目以降の説明
if(numbers[j]+numbers[j+1]<numbers[j+1]+numbers[j]) swap(numbers[j],numbers[j+1]);
↑の理由
97799999と999とか前の部分(977と999)が等しくない場合
ーーーーー明らかに999のほうが前
33355555と333とか前の部分(333)が等しい場合
ーーーーー並べ方は2通りしかないんだから試せばいい
バブルソートっていうところがミソ
分からなかったんだけどTopCoder Statisticsで分かった
#include <algorithm> #include <cstdio> #include <sstream> #include <string> #include <vector> using namespace std; class TheNumbersLord { public: string create(vector <int> input, int n) { int m=input.size(),greatest=0; string gs; vector<string> numbers(m); for(int i=0;i<m;i++) { char s[100]; sprintf(s,"%d",input[i]); numbers[i]=s; if(greatest<input[i]) gs=numbers[i],greatest=input[i]; } printf("gs=%s",gs.c_str()); while(numbers.size()!=n) numbers.push_back(gs); //ここから説明します for(int i=0;i<n;i++) for(int j=0;j<n-1;j++) if(numbers[j]+numbers[j+1]<numbers[j+1]+numbers[j]) swap(numbers[j],numbers[j+1]); string ans; for(int i=0;i<n;i++) ans+=numbers[i]; return ans; } };
SRM304 div2 hard
· 元記事
幾何+DP。
問題文見て「あ、幾何や。やめとこ。」と思い、すぐに解説を探したけど幾何の部分も大したことはなかった。

DPも大したことはないけど、1点だけ注意が必要。
それは頂点[0]と頂点[n-1]は隣り合っているので同時に使ってはいけないという点。
参考にさせていただいたページ:
SRM 304 PolyMove - hama_DU@TopCoderへの道 - TopCoder部
#include <cmath> #include <vector> using namespace std; class PolyMove { public: double getInc(double x1,double y1,double x2,double y2) { double xd=x1-x2,yd=y1-y2; return sqrt(xd*xd+yd*yd)/2.0; } double addedArea(vector <int> _x, vector <int> _y) { int n=_x.size(); vector<double> x(n),y(n),inc(n); for(int i=0;i<n;i++) x[i]=_x[i],y[i]=_y[i]; for(int i=1;i<n-1;i++) inc[i]=getInc(x[(i-1+n)%n],y[(i-1+n)%n],x[(i+1+n)%n],y[(i+1+n)%n]); double ans; double DP[55]; //inc[n-1]を使う場合 DP[n]=0.0; //これは番兵的存在 DP[n-1]=inc[n-1]; for(int i=n-2;1<=i;i--) DP[i]=max(DP[i+2]+inc[i],DP[i+1]); //inc[i]を使うならinc[i+1]は使えない ans=DP[1]; //inc[0]を使う場合 DP[n]=0.0; DP[n-1]=0.0; for(int i=n-2;0<=i;i--) DP[i]=max(DP[i+2]+inc[i],DP[i+1]); ans=max(ans,DP[0]); return ans; } };
SRM397 div2 hard
· 元記事
メモ化再帰。
rec(残っているマーブル、今使っているバッグの残り容量、まだ使っていないバッグ)をメモ化。
#include <cstring> #include <vector> using namespace std; class CollectingMarbles { public: int m,bC; vector<int> mW; int cache[(1<<13)][21][10]; int rec(int marble,int cur,int left) { if(cache[marble][cur][left]!=-1) return cache[marble][cur][left]; if(marble==0 || (cur==0 && left==0)) return cache[marble][cur][left]=0; int r=(left!=0)?rec(marble,bC,left-1):0; for(int i=0;i<m;i++) if(marble & (1<<i) && mW[i]<=cur) r=max(r,rec(marble^(1<<i),cur-mW[i],left)+1); return cache[marble][cur][left]=r; } int mostMarbles(vector <int> marblesWeights, int bagCapacity, int numberOfBags) { bC=bagCapacity,m=marblesWeights.size(); mW=marblesWeights; memset(cache,-1,sizeof(cache)); return rec((1<<m)-1,bagCapacity,numberOfBags-1); } };
SRM367 div2 hard
· 元記事
メモ化再帰するだけ。
rec(s)でs以降のデータを送るときの最小のパケット数を求める。
実際はオフセットデータも送らないといけなんじゃね。
#include <algorithm> #include <climits> #include <cstdio> #include <cstring> #include <vector> #include <utility> typedef long long LL; using namespace std; class ProgrammingDevice { public: int n; LL maxData; LL cache[50]; vector<pair<int,int> > data; LL rec(int s) { if(s==n) return 0; if(cache[s]!=-1) return cache[s]; cache[s]=LLONG_MAX; for(int e=s;e<n;e++) { LL r=0; LL d=data[e].first+data[e].second-data[s].first; r=d/maxData+(d%maxData==0)?0:1+rec(e+1); cache[s]=min(cache[s],r); } return cache[s]; } int minPackets(vector <int> offset, vector <int> size, int _maxData) { n=offset.size(); maxData=_maxData; for(int i=0;i<n;i++) data.push_back(make_pair(offset[i],size[i])); sort(data.begin(),data.end()); return rec(0); } };
SRM368 div2 hard
· 元記事
一応ACするけど、すごく頭の悪いコード。
practice roomのrngさんのコードを見たほうがいいと思う。
でも一応解説。
1:まずマスの1つ1つを頂点としてグラフを作る。
2:このとき隣接リストで持たないとTLEする。(理由:各頂点から出る枝は多くても4本しかない)
3:次に各々の頂点からジャンプできる頂点を深さ優先探索で全部調べる。(check)
4:その結果を使って頂点0から到達可能な循環路があるかどうかしらべる。(あったら-1を返す。)
5:深さ優先探索で最長のジャンプ経路を調べる。このときメモ化しないとTLEする。
#include <cstring> #include <set> #include <string> #include <vector> using namespace std; class JumpingBoard { public: int n,start; vector<int> map[2500]; bool can[2500][2500]; int cache[2500]; void check(int p) { if(!can[start][p]) { can[start][p]=true; for(int i=0;i<(int)map[p].size();i++) check(map[p][i]); } } int DFS(int now) { if(cache[now]!=-1) return cache[now]; int r=0; for(int i=0;i<map[now].size();i++) r=max(r,DFS(map[now][i])); return cache[now]=r+1; } int maxJumps(vector <string> board) { int h=board.size(),w=board[0].size(); memset(can,false,sizeof(can)); memset(cache,-1,sizeof(cache)); n=h*w; for(int y=0;y<h;y++) for(int x=0;x<w;x++) if(board[y][x]!='H') { int p=y*w+x; int d=board[y][x]-'0'; int mv[4][2]={{d,0},{-d,0},{0,d},{0,-d}}; for(int i=0;i<4;i++) { int yy=y+mv[i][0],xx=x+mv[i][1]; int pp=yy*w+xx; if(0<=yy && yy<h && 0<=xx && xx<w && board[yy][xx]!='H') map[p].push_back(pp); } } //printf("check-start\n"); for(int i=0;i<n;i++) { //printf("i=%d\n",i); start=i; check(i); } //printf("check-end\n"); for(int i=0;i<n;i++) for(int j=i+1;j<n;j++) if(can[0][i] && can[i][j] && can[j][i]) return -1; int ans=DFS(0); return ((1<<28)<=ans)?-1:ans; } };
SRM427 div2 hard
· 元記事
全探索。
a,c,i,n,tは必ず学ばなければならないので別にして、他の文字はbitで探索。
ACRushさんは
for(int set=mask;1;set=(set-1)&mask)
って書いてた、賢い。
#include <string> #include <vector> using namespace std; class Teaching { public: int popCount(int n) { int r=0; for(int i=0;i<30;i++) if(n & (1<<i)) r++; return r; } bool acint(char c) { return (c=='a' || c=='c' || c=='i' || c=='n' || c=='t'); } int c2i(char c) { int abc[26],r=0; for(int i=0;i<26;i++) abc[i]=(acint('a'+i))?0:1; for(int i=0;i<=c-'a';i++) r+=abc[i]; return r-1; } int count(vector <string> words, int K) { int n=words.size(); if(K<5) return 0; K-=5; vector<int> w(n,0); for(int i=0;i<n;i++) for(int j=0;j<(int)words[i].size();j++) if(!acint(words[i][j])) w[i]|=(1<<c2i(words[i][j])); int ans=0; for(int learn=0;learn<(1<<21);learn++) if(popCount(learn)==K) { int aa=0; for(int i=0;i<n;i++) if((w[i]&learn)==w[i]) aa++; ans=max(ans,aa); } return ans; } };
SRM408 div2 hard
· 元記事
久しぶりに1発で通した。
4000*4000のメモリーは確保できないのでうまいこと節約しませう。
#include <algorithm> using namespace std; class MarblesInABag { public: double DP[2][4000+1]; double getProbability(int redCount, int blueCount) { for(int R=0;R<=redCount;R++) for(int B=0;B<=blueCount;B++) { if((R+B)%2==0) continue; if(R==0) DP[R%2][B]=1.0; else if(B==0) DP[R%2][B]=0.0; else if(B==1) DP[R%2][B]=0.0; else DP[R%2][B]=(double)R/(double)(R+B)*DP[(R-1+2)%2][B-1]+(double)B/(double)(R+B)*DP[R%2][B-2]; if(R==redCount && B==blueCount) return DP[R%2][B]; } return -1.0; } };
SRM332 div2 hard
· 元記事
えらく簡単だった。
0<=xdだけど0
#include <string> #include <vector> using namespace std; class Squares { public: int countSquares(vector <string> field) { int ans=0,h=field[0].size(),w=field.size(),n=55; for(int x1=0;x1<n;x1++) for(int y1=0;y1<n;y1++) for(int xd=0;xd<n;xd++) for(int yd=1;yd<n;yd++) { int x2=x1+xd,y2=y1+yd; int x3=x1+yd,y3=y1-xd; int x4=x1+yd+xd,y4=y1-xd+yd; if(0<=x1 && x1<w && 0<=y1 && y1<h && 0<=x2 && x2<w && 0<=y2 && y2<h && 0<=x3 && x3<w && 0<=y3 && y3<h && 0<=x4 && x4<w && 0<=y4 && y4<h && field[x1][y1]==field[x2][y2] && field[x1][y1]==field[x3][y3] && field[x1][y1]==field[x4][y4]) ans++; } return ans; } };
SRM405 div2 hard
· 元記事
惜しかった。もうちょっと粘れば正解にたどり着けたかもしれない。
わからなかったのはこの部分
for(int next=startIndex.back()+1;0<=left-next && next<=(length-left+1);next++)
この文があると左から埋めていったときに穴ができる可能性を減らせるから。
next<=(length-left+1)
が入る。
#include <string> #include <vector> using namespace std; class IdealString { public: string ans; int length; string calc(vector<int> startIndex) { int nS=startIndex.size(); string r(length,'#'); vector<int> left=startIndex; for(int i=1;i<nS;i++) r[startIndex[i]-1]='A'+i-1,left[i]--; for(int i=1;i<nS;i++) for(int j=startIndex[i];j<length;j++) if(r[j]=='#' && 0<left[i]) r[j]='A'+i-1,left[i]--; //printf("%s\n",r.c_str()); return (r.find('#')==string::npos)?r:string(length,'Z'+1); } void rec(vector<int> startIndex,int left) { if(left==0) { /*for(int i=0;i<startIndex.size();i++) printf("%d ",startIndex[i]); printf("\n");*/ ans=min(ans,calc(startIndex)); return; } vector<int> v=startIndex; int last=v.size(); v.push_back(0); for(int next=startIndex.back()+1;0<=left-next && next<=(length-left+1);next++) { v[last]=next; rec(v,left-next); } return; } string construct(int _length) { length=_length; ans=string(length,'Z'+1); rec(vector<int>(1,0),length); return (ans==string(length,'Z'+1))?"":ans; } };
方針がわからないときに見る記事
· 元記事
全探索、DP,貪欲、二分探索、数学
SRM431 div2 hard
· 元記事
全く歯が立たない。
探索だった。div2hardで初めて見た。
藪から棒だ。亀も木から落ちる。
#include <cmath> class SumAndProduct { public: int smallestSet(int S, int P) { if(S==P) return 1; double s=S,p=P; for(int n=2;n<=100;n++) if(p<pow(s/n,n)+1e-9) return n; return -1; } };
SCOTLAND YARD CONTEST
· 元記事
SCOTLAND YARD CONTEST
というのが開催されるらしい。泥棒のAIならなんとかなる気がする。
1:まずmap[200][200]みたいな感じで警察からのコストを計算する
2:身近で一番コストの低いところに逃げる
…テキトーだ
SRM424 div2 hard
· 元記事
最小全域木を求める。
存在しなかったら空のvectorを返す。
存在したら道の本数がMになるまで付け足す。
UNIONFINDのコードはコピーしたので集合のサイズを求める機能が付いてる。
#include <utility> #include <set> #include <vector> #include <algorithm> #include <string> using namespace std; class UNIONFIND { vector<int> date; vector<int> scale; public: UNIONFIND(int n) { for(int i=0;i<n;i++) { date.push_back(i); scale.push_back(1); } } int root(int n) { if(date[n]==n) return n; else return date[n]=root(date[n]); } void unionSet(int x,int y) { if(root(x)==root(y)) return; scale[root(y)]+=scale[root(x)]; scale[root(x)]=0; date[root(x)]=date[root(y)]; } int unionSize(int n) { return scale[root(n)]; } }; class BestRoads { public: vector <int> numberOfRoads(vector <string> input, int M) { int Rn=0,In=input.size(); vector<pair<int,int> > road; for(int i=0;i<In;i++) for(int j=i+1;j<In;j++) if(input[i][j]=='Y') Rn++,road.push_back(make_pair(i,j)); vector<bool> used(Rn,false); UNIONFIND U(In); vector<int> ans(In,0); int add=0; for(int i=0;i<Rn;i++) if(U.root(road[i].first)!=U.root(road[i].second)) { U.unionSet(road[i].first,road[i].second); used[i]=true; ans[road[i].first]++; ans[road[i].second]++; add++; } int id=U.root(0); for(int i=0;i<In;i++) if(U.root(i)!=id) return vector<int>(0); if(add==M) return ans; for(int i=0;i<Rn;i++) if(!used[i]) { ans[road[i].first]++; ans[road[i].second]++; add++; if(add==M) return ans; } return vector<int>(0); } };
gnuplot-4.4.3を使ってみた
· 元記事
SRM433 div2 hard
· 元記事
愚直にレシピをn^2回更新すればよい。
#include <iostream> #include <map> #include <sstream> #include <string> #include <vector> typedef long long LL; using namespace std; class MakingPotions { public: int getCost(vector <string> marketGoods, vector <int> cost, vector <string> recipes) { int n=recipes.size(); map<string,LL> ans; vector<string> result(n); vector<vector<string> > ingredient(n,vector<string>(0)); vector<vector<LL> > amount(n,vector<LL>(0)); for(int i=0;i<(int)marketGoods.size();i++) ans[marketGoods[i]]=cost[i]; for(int i=0;i<n;i++) { for(int j=0;j<recipes[i].size();j++) { char c=recipes[i][j]; recipes[i][j]=(c=='=' || c=='+')?' ':c; } string s; int a; stringstream ss(recipes[i]); ss >> result[i]; cout << result[i]; while(ss >> a >> s) { cout << " " << a << " " << s; amount[i].push_back(a); ingredient[i].push_back(s); } cout << endl; } for(int time=0;time<n;time++) for(int i=0;i<n;i++) { bool b=true; for(int j=0;j<(int)ingredient[i].size();j++) if(ans.find(ingredient[i][j])==ans.end()) b=false; if(!b) continue; LL a=0; for(int j=0;j<(int)ingredient[i].size();j++) a=min((LL)1000000001,a+amount[i][j]*ans[ingredient[i][j]]); if(ans.find(result[i])!=ans.end()) ans[result[i]]=min(ans[result[i]],a); else ans[result[i]]=min((LL)1000000001,a); cout << "a=" << a << endl; } return (int)(ans.find("LOVE")!=ans.end())?ans["LOVE"]:-1; } };
gnuplot-4.4.3をインストールしてみた
· 元記事
使いもしないソフトをインストールする練習第n弾
INSTALLファイルに書いてあるけどubuntuの人は./configureする前に
ln -s /usr/lib/pkgconfig/lua5.1.pc /usr/lib/pkgconfig/lua.pc
を実行しないとmakeできない。
ほっといても害があるものなのかどうかはよくわからないけどとりあえずはこのままにしておく。
SRM428 div2 hard
· 元記事
nCrはパスカルの三角形で求められる。
あとオーバーフローするので
C[i][j]=min(k+1,C[i-1][j]+C[i][j-1]);
としなくてはならない。
#include <string> typedef long long LL; using namespace std; class TheDictionary { public: LL C[110][110]; string find(int n, int m, int _k) { LL k=_k; int len=n+m; string ans; for(int i=0;i<110;i++) for(int j=0;j<110;j++) if(i==0 || j==0) C[i][j]=1; else C[i][j]=min(k+1,C[i-1][j]+C[i][j-1]); for(int i=0;i<len;i++) { LL aN=(n!=0)?C[m][n-1]:0; LL zN=(m!=0)?C[n][m-1]:0; if(k<=aN) ans+='a',n--; else if(k<=aN+zN) ans+='z',k-=aN,m--; else return ""; } return ans; } };
SRM501 div1 感想
· 元記事
#7零点
SRM372 div2 hard
· 元記事
501にボコボコにされて意気消沈してるから(読んでるひといないと思うけど)
解説はない
ってうかディスプレイの文字が霞んで見えるんだよ。もうだめだよ。
conkyのuptimeが15時間超えてるよ。やばいよ。
#include <string> #include <queue> #include <vector> #include <utility> using namespace std; typedef pair<int,pair<int,int> > D; D makeD(int a,int b,int c) { return make_pair(a,make_pair(b,c)); } class RainyDay { public: int n; int wetTime[50][50]; string path,forecast; int rainy(int t,int p) { return (path[p]=='.' && forecast[t%n+p]=='R')?1:0; } int minimumRainTime(string _path, string _forecast) { int home,first,mv; path=_path; forecast=_forecast+_forecast; n=path.size(); for(int i=0;i<n;i++) { if(path[i]=='H') home=i; if(path[i]=='Y') first=i; } mv=(first<home)?1:-1; for(int i=0;i<50;i++) for(int j=0;j<50;j++) wetTime[i][j]=(1<<28); priority_queue<D,vector<D>,greater<D> > que; que.push(makeD(0,0,first)); while(!que.empty()) { int w=que.top().first; int t=que.top().second.first,p=que.top().second.second; que.pop(); if(t<250 && 0<=p && p<n && w<wetTime[t][p]) { wetTime[t][p]=w; int ww; ww=w+rainy(t,p)+rainy(t+1,p); que.push(makeD(ww,(t+1)%n,p)); ww=w+rainy(t,p+1)+rainy(t+1,p+1); que.push(makeD(ww,(t+1)%n,p+1)); ww=w+rainy(t,p-1)+rainy(t+1,p-1); que.push(makeD(ww,(t+1)%n,p-1)); } } int minWet=(1<<28); for(int i=0;i<50;i++) minWet=min(minWet,wetTime[i][home]); return minWet; } };
AOJ0047
· 元記事
#include <iostream> using namespace std; int main() { int cup[3]; cup[0]=1,cup[1]=0,cup[2]=0; string s; while(cin >> s) swap(cup[s[0]-'A'],cup[s[2]-'A']); if(cup[0]==1) cout << "A" << endl; if(cup[1]==1) cout << "B" << endl; if(cup[2]==1) cout << "C" << endl; return 0; }
SRM385 div2 hard
· 元記事
数学ゲーは嫌い。
また敗北した。
”自然数a,b,mがあって、lcm(a,b)<=mかつm%gcd(a,b)==0のときm=a*x+b*yとなる整数x,yが存在する。”
というのが
"a,bが互いに素なら1=a*x+b*yとなるx,yが存在する"
というのをもとにして成立する
#include <cstdio> #include <algorithm> using namespace std; typedef long long LL; class SummingArithmeticProgressions { public: int gcd(int a,int b) { if(b==0) return a; else return gcd(b,a%b); } int lcm(int a,int b) { return a*b/gcd(a,b); } int popCount(LL b) { int r=0; for(LL i=0;i<60;i++) if(b & (LL(1)<<i)) r++; return r; } int calc(int n,int k) { int a0=k*(k+1)/2,r=0,a=k,b=k*(k-1)/2,t; LL bit=0; n-=a0; if(n<0) return 0; t=(n-lcm(a,b))/gcd(a,b); if(0<t) r+=t; for(int i=0;i*a<=min(lcm(a,b),n);i++) for(int j=0;i*a+j*b<=min(lcm(a,b),n);j++) { LL x=i*a+j*b; //printf("x=%lld\n",x); bit |= ((LL)1<<x); } r+=popCount(bit); printf("a=%d b=%d n=%d a0=%d gcd(a,b)=%d lcm(a,b)=%d popCount(bit)=%d r=%d\n",a,b,n,a0,gcd(a,b),lcm(a,b),popCount(bit),r); return r; } int howMany(int left, int right, int k) { return calc(right,k)-calc(left-1,k); } };
SRM448 div2 hard
· 元記事
メモ化再起。マークでは色なので気をつけてください。
#include <cstring> #include <string> #include <vector> #define MOD 1234567891 typedef long long LL; using namespace std; class TheCardLineDivTwo { public: int n; LL cache[13][2][(1<<16)]; vector<string> cards; char m2n(char c) { if(c=='S') return 0; if(c=='C') return 0; if(c=='D') return 1; if(c=='H') return 1; return -1; } char n2n(char c) { if(c=='A') return 0; if('2'<=c && c<='9') return c-'1'; if(c=='T') return 9; if(c=='J') return 10; if(c=='Q') return 11; if(c=='K') return 12; return -1; } LL rec(char num,char mark,int left) { LL &r=cache[(int)num][(int)mark][left]; if(r!=-1) return r; if(left==0) return r=1; r=0; for(int next=0;next<n;next++) if( left&(1<<next) && (cards[next][0]==num || cards[next][1]==mark) ) r=(r+rec(cards[next][0],cards[next][1],left^(1<<next)))%MOD; return r; } int count(vector <string> _cards) { cards=_cards; n=cards.size(); for(int i=0;i<n;i++) { cards[i][0]=n2n(cards[i][0]); cards[i][1]=m2n(cards[i][1]); } memset(cache,-1,sizeof(cache)); LL ans=0; for(int i=0;i<n;i++) ans=(ans+rec(cards[i][0],cards[i][1],((1<<n)-1)^(1<<i)))%MOD; return (int)ans; } };
SRM355 div2 hard
· 元記事
自作クラスでsortとかを使う場合は
bool operator<(const C &c) const{ return hoge; }
ってしないとコンパイルできない
#include <algorithm> #include <vector> using namespace std; class LIQUID { public: int p,a; bool operator<(const LIQUID &l) const{ return p<l.p; } }; class MixingLiquids { public: double howMuch(vector <int> percent, vector <int> amount, int need) { vector<LIQUID> dense; vector<LIQUID> thin; double add=0.0; for(int i=0;i<(int)percent.size();i++) { LIQUID l; l.p=percent[i],l.a=amount[i]; if(need<percent[i]) dense.push_back(l); else if(percent[i]<need) thin.push_back(l); else add+=amount[i]; } int totalSub,a,b; for(int i=0;i<(int)dense.size();i++) a+=(dense[i].p-need)*dense[i].a; for(int i=0;i<(int)thin.size();i++) b+=(need-thin[i].p)*thin[i].a; totalSub=min(a,b); double dAmount=0,tAmount=0; a=totalSub,b=totalSub; sort(dense.begin(),dense.end()); for(int i=0;0<a && i<(int)dense.size();i++) { if(0<=a-(dense[i].p-need)*dense[i].a) { dAmount+=dense[i].a; a-=(dense[i].p-need)*dense[i].a; } else { dAmount+=(double)a/(double)(dense[i].p-need); a=0; } } sort(thin.begin(),thin.end()); reverse(thin.begin(),thin.end()); for(int i=0;0<b && i<(int)thin.size();i++) { if(0<=b-(need-thin[i].p)*thin[i].a) { tAmount+=thin[i].a; b-=(need-thin[i].p)*thin[i].a; } else { tAmount+=(double)b/(double)(need-thin[i].p); b=0; } } return dAmount+tAmount+add; } };
SRM430 div2 hard
· 元記事
久しぶりにマイ・ドストライクコースことメモ化再起するだけの問題
#include <cstring> #include <string> #include <vector> using namespace std; class ImageTraders { public: int N; vector<string> price; int cache[20][20][(1<<15)]; int rec(int from,int nowPrice,int left) { int &r=cache[from][nowPrice][left]; if(r!=-1) return r; r=1; for(int next=0;next<N;next++) if(next!=from && left&(1<<next) && nowPrice<=price[from][next]) r=max(r,1+rec(next,price[from][next],left^(1<<next))); return r; } int maxOwners(vector <string> _price) { price=_price; N=price.size(); memset(cache,-1,sizeof(cache)); return rec(0,'0',(1<<N)-1-1); } };
xtwitterをインストールした
· 元記事
Xtwitter - cuspy wiki その 5
ソースからコンパイルに挑戦したんだけど失敗したので.debを使った
SRM373 div2 hard
· 元記事
殺す気か!めちゃめちゃしんどい。
線分の交差判定は検索して出てきた
(x1-x2)*(y-y1)+(y1-y2)*(x1-x)
これを使ったんだけど線分と長方形の辺が一致する場合のみ別に考える必要がある
参考にさせていただいたサイト:
Algorithmの中の”第16回 もっと簡単に−線分交差判定−”
(紹介してもよいということなのでリンクを貼っていますが問題があれば削除します)
#include <sstream> #include <string> #include <vector> typedef long long LL; using namespace std; class REC { public: LL left,down,right,up; }; bool contain(REC r,LL x,LL y) { return (r.left<x && x<r.right && r.down<y && y<r.up); } LL calc(REC r,LL x,LL y) { return (r.left-r.right)*(y-r.down)+(r.down-r.up)*(r.left-x); } bool intersect(REC r,REC s) { if(s.left==s.right && (s.left==r.left || s.left==r.right) ) return !(s.up<r.down || r.up<s.down); else if(s.down==s.up && (s.up==r.down || s.down==r.up) ) return !(s.right<r.left || r.right<s.left); else return calc(r,s.left,s.down)*calc(r,s.right,s.up)<=0 && calc(s,r.left,r.down)*calc(s,r.right,r.up)<=0; } class RectangleCrossings { public: vector <int> countAreas(vector <string> rectangles, vector <string> segments) { int rN=rectangles.size(); int sN=segments.size(); vector<REC> rec(rN); vector<REC> seg(sN); for(int i=0;i<rN;i++) { stringstream ss(rectangles[i]); ss >> rec[i].left >> rec[i].down >> rec[i].right >> rec[i].up; } for(int i=0;i<sN;i++) { stringstream ss(segments[i]); ss >> seg[i].left >> seg[i].down >> seg[i].right >> seg[i].up; } vector<int> ans(2,0); for(int i=0;i<rN;i++) { bool e=false; bool s=false; for(int j=0;j<sN;j++) { if(contain(rec[i],seg[j].left,seg[j].down) || contain(rec[i],seg[j].right,seg[j].up)) e=true; REC r; r.left=rec[i].left,r.down=rec[i].down; r.right=rec[i].right,r.up=rec[i].down; if(intersect(r,seg[j])) { //printf("down\n"); s=true; } r.left=rec[i].left,r.down=rec[i].up; r.right=rec[i].right,r.up=rec[i].up; if(intersect(r,seg[j])) { //printf("up\n"); s=true; } r.left=rec[i].left,r.down=rec[i].down; r.right=rec[i].left,r.up=rec[i].up; if(intersect(r,seg[j])) { //printf("left\n"); s=true; } r.left=rec[i].right,r.down=rec[i].down; r.right=rec[i].right,r.up=rec[i].up; if(intersect(r,seg[j])) { //printf("right\n"); s=true; } } int S=(rec[i].up-rec[i].down)*(rec[i].right-rec[i].left); if(e) ans[0]+=S; else if(s) ans[1]+=S; } return ans; } };
conkyをインストールしてみた(ソースから)
· 元記事
2000 sudo apt-get update
2002 mkdir work
2004 cd work
2006 mv /home/hogeo/Downloads/conky-1.8.1.tar.bz2 .
2008 tar jxvf conky-1.8.1.tar.bz2
2010 cd conky-1.8.1/
2016 sudo apt-get install libx11-dev
2023 sudo apt-get install lua5.1
2029 sudo apt-get install liblua5.1-0-dev
2035 sudo apt-get install libxext-dev
2040 sudo apt-get install libxdamage-dev
2044 sudo apt-get install libxft-dev
2049 sudo apt-get install libglib2.0-dev
2050 ./configure
2051 make
2053 sudo make install
2055 conky
hogeoは仮の名前です
AOJ0117
· 元記事
ワーシャルフロイドするだけ。
scanf("%d,%d,%d,%d"......が便利だった
#include <algorithm> #include <cstdio> using namespace std; int dist[20][20]; int main() { int n,m; scanf("%d%d",&n,&m); for(int i=0;i<n;i++) for(int j=0;j<n;j++) dist[i][j]=(i==j)?0:(1<<28); for(int i=0;i<m;i++) { int a,b,c,d; scanf("%d,%d,%d,%d",&a,&b,&c,&d); a--,b--; dist[a][b]=c; dist[b][a]=d; } for(int k=0;k<n;k++) for(int i=0;i<n;i++) for(int j=0;j<n;j++) dist[i][j]=min(dist[i][j],dist[i][k]+dist[k][j]); int x1,x2,y1,y2; scanf("%d,%d,%d,%d",&x1,&x2,&y1,&y2); x1--,x2--; printf("%d\n",y1-y2-dist[x1][x2]-dist[x2][x1]); return 0; }
clive インストールしてみた
· 元記事
最新のバージョン(2.3)ではうまく動かなかったので
2.2でやったらうまいこと行った
実行したコマンドーー記録してませんでしたすいません
tar zxvf clive-2.2.23.tar.gzしたらMakefile.PLのなかに必要なものが書いてあった
アンインストールは/usr/local/bin/cliveを削除
SRM382 div2 hard
· 元記事
難しかった。
まずある数列の
(偶数番目の要素の和)=(奇数番目の要素の和)である集合をP
(前半の和)=(後半の和)である集合をQとする
DP[i][j]をi個の要素を持ち和がjである数列の数だとする(並び替えは違うものとする)
するとn(P)+n(Q)はDPで求められる。(*1)
次にn(P∩Q)を求める
前半K個の和をhalf、その中で偶数番目のものの和をquarterとする
(*2)
それでn(P)+n(Q)-n(P∩Q)を返す
#include <cstring> #include <string> #define MOD 999983 typedef long long LL; using namespace std; class CharmingTicketsEasy { public: LL DP[70][500]; int count(int K, string good) { memset(DP,0,sizeof(DP)); DP[0][0]=1; for(int i=0;i<K;i++) for(int j=0;j<460;j++) for(int k=0;k<(int)good.size();k++) { DP[i+1][j+good[k]-'0']+=DP[i][j]; DP[i+1][j+good[k]-'0']%=MOD; } LL U=0; for(int i=0;i<460;i++) //n(P)+n(Q)をUとしている U=(U+(DP[K][i]*DP[K][i])*2)%MOD; //*1 LL P=0; for(int half=0;half<460;half++) for(int quarter=0;quarter<=half;quarter++) { LL a=DP[K-K/2][quarter]*DP[K/2][half-quarter]; a%=MOD; //Pはn(P∩Q) P=(P+a*a)%MOD; //*2 } return ((U-P)%MOD+MOD)%MOD; } };
AOJ0105
· 元記事
setとmap使えば自動的にソートしてくれるのでらくちん
#include <iostream> #include <map> #include <set> #include <string> using namespace std; int main() { map<string,set<int> > index; string s; while(cin >> s) { int a; cin >> a; index[s].insert(a); } map<string,set<int> >::iterator mitr; for(mitr=index.begin();mitr!=index.end();mitr++) { cout << (*mitr).first << endl; set<int>::iterator sitr; for(sitr=(*mitr).second.begin();sitr!=(*mitr).second.end();sitr++) { if(sitr!=(*mitr).second.begin()) cout << " "; cout << (*sitr); } cout << endl; } return 0; }
AOJ0107
· 元記事
解説のしようがない。消しゴムでも使って実験してみるといいのでは?
#include <cmath> #include <cstdio> #include <iostream> using namespace std; void solve(int h,int w,int s) { int n; double r; r=sqrt(h*h+w*w); r=min(r,sqrt(h*h+s*s)); r=min(r,sqrt(w*w+s*s)); cin >> n; for(int i=0;i<n;i++) { int a; cin >> a; a*=2; if(r<(double)a) cout << "OK" << endl; else cout << "NA" << endl; } } int main() { int h,w,s; while(1) { cin >> h >> w >> s; if(h==0 && w==0 && s==0) break; solve(h,w,s); } return 0; }
AOJ0112
· 元記事
引っ掛け問題。訴えてやる。(LLにしないと溢れます。)
#include <iostream> #include <vector> #include <algorithm> typedef long long LL; using namespace std; void solve(int n) { LL r=0; vector<LL> v(n); for(int i=0;i<n;i++) cin >> v[i]; sort(v.begin(),v.end()); for(int i=1;i<n;i++) { r+=v[i-1]; v[i]+=v[i-1]; } cout << r << endl; } int main() { int n; while(1) { cin >> n; if(n==0) break; solve(n); } return 0; }
参考にさせていただきました
AOJ 0112 A Milk Shop - kyuridenamidaのチラ裏
SRM386 div2 hard
· 元記事
挫折。
SRM497 div2 hard
· 元記事
めっちゃ時間がかかったけど自分で解法を見つけた。
まずinsertは必要ない。(deleteで代用できる)
次にSを前半と後半に分け(最大50通り)その2つを等しくするための最小値を
求める。
先頭同士が同じならその先頭は無視して次を考える
if(s[i]==t[j]) DP[i][j]=DP[i+1][j+1]
違うならどちらかをdeleteもしくはどちらかをchangeする。
changeするならどちらをchangeしても同じなので
else DP[i][j]=min(DP[i+1][j],min(DP[i][j+1],DP[i+1][j+1])+1
となる
#include <cstring> #include <string> using namespace std; class MakeSquare { public: string s,t; int n,m; int DP[50][50]; int calc() { for(int i=n;0<=i;i--) for(int j=m;0<=j;j--) { if(i==n || j==m) DP[i][j]=n+m-i-j; else if(s[i]==t[j]) DP[i][j]=DP[i+1][j+1]; else DP[i][j]=min(DP[i+1][j],min(DP[i][j+1],DP[i+1][j+1]))+1; } return DP[0][0]; } int minChanges(string S) { int ans=(1<<28); int Sn=S.size(); for(int i=0;i<Sn;i++) { s=S.substr(0,i),t=S.substr(i,Sn-i); n=i,m=Sn-i; ans=min(ans,calc()); } return ans; } };
SRM496 div2 hard
· 元記事
文字列は生成しない方向で頑張っていたんだけど無理だった。
まあ理解はした。
SRM500 div1 easy
· 元記事
まずソートして1番票をもらう人をもとめる。(ソースではNmost人としている)
それから"vulnerable"な人の人数が一人になるか変わらなくなるまで
シュミレーションする。
#include <algorithm> #include <vector> using namespace std; class MafiaGame { public: double probabilityToLose(int N, vector<int> decisions) { vector<int> v(N,0); for(int i=0;i<(int)decisions.size();i++) v[decisions[i]]++; sort(v.begin(),v.end()); reverse(v.begin(),v.end()); int most=v[0],Nmost=0; for(int i=0;i<N;i++) if(v[i]==most) Nmost++; if(most==1) return 0.0; if(Nmost==1) return 1.0; int preMost=Nmost; while(1) { int m=preMost; int left=N-m*most; if(left%m==0) return 0.0; else if(left%m==1) return 1.0/(double)Nmost; else m=left%m; preMost=m; } } };
SRM500 div1 感想
· 元記事
easyだけ通して100.46点。1395から1519。
SRM416 div2 hard
· 元記事
十八番のメモ化再帰。
#include <algorithm> #include <cstring> #include <string> #include <vector> using namespace std; class DancingCouples { public: vector <string> can; int bn,gn; int cache[11][11][(1<<10)]; int rec(int b,int K,int left) { int &r=cache[b][K][left]; if(r!=-1) return r; if(K==0) return r=1; if(b==bn) return r=0; r=0; r+=rec(b+1,K,left); for(int g=0;g<gn;g++) if((left&(1<<g)) && can[b][g]=='Y') r+=rec(b+1,K-1,left&(~(1<<g))); return r; } int countPairs(vector<string> _canDance,int K) { can=_canDance; bn=can.size(),gn=can[0].size(); memset(cache,-1,sizeof(cache)); return rec(0,K,(1<<gn)-1); } };
SRM403 div2 hard
· 元記事
丸写し上等!
・・・・・・・解けへんかった
#include <algorithm> #include <utility> #include <vector> #define INF (1<<29) using namespace std; class TheSumOfLuckyNumbers { public: int DP[1000000+1]; bool isLucky(int n) { while(0<n) if(n%10!=4 && n%10!=7) return false; else n/=10; return true; } vector <int> sum(int n) { vector<int> v; for(int i=1;i<1000000;i++) if(isLucky(i)) v.push_back(i); for(int i=0;i<=1000000;i++) DP[i]=INF; DP[0]=0; for(int i=0;i<=1000000;i++) for(int j=0;j<v.size();j++) if(0<=i-v[j]) DP[i]=min(DP[i],DP[i-v[j]]+1); int cnt=DP[n]; vector<int> ans; if(cnt==INF) return ans; while(0<cnt) { int i; for(i=0;;i++) if(DP[n-v[i]]==cnt-1) break; ans.push_back(v[i]); n-=v[i]; cnt--;; } return ans; } };
SRM347 div2 hard
· 元記事
巡回セールスマン+ビットで全探索なんだけど
"~mask"を"(1<
#include <sstream> #include <string> #include <vector> using namespace std; class TaxiManager { public: int dist[50][50]; int DP[12][(1<<12)]; int cost[(1<<12)]; int schedule(vector<string> roads, vector<string> _customers) { int n=roads.size(); int cn=_customers.size(); vector<int> from(cn); vector<int> to(cn); for(int i=0;i<n;i++) { for(int j=0;j<n;j++) if(i==j) dist[i][j]=0; else if(roads[i][j]!='0') dist[i][j]=roads[i][j]-'0'; else dist[i][j]=(1<<28); } for(int k=0;k<n;k++) for(int i=0;i<n;i++) for(int j=0;j<n;j++) dist[i][j]=min(dist[i][j],dist[i][k]+dist[k][j]); for(int i=0;i<cn;i++) { stringstream ss(_customers[i]); ss >> from[i] >> to[i]; } for(int i=0;i<(1<<cn);i++) for(int j=0;j<cn;j++) DP[j][i]=(1<<28); for(int state=0;state<(1<<cn);state++) for(int now=0;now<cn;now++) if(state & (1<<now)) { DP[now][state]=dist[from[now]][to[now]]; int add; if(state==(1<<now)) add=dist[to[now]][0]; else { add=(1<<28); for(int next=0;next<cn;next++) if((state & (1<<next)) && next!=now) add=min(add,dist[to[now]][from[next]]+DP[next][(state - (1<<now))]); } DP[now][state]+=add; } for(int mask=0;mask<(1<<cn);mask++) { cost[mask]=(1<<28); for(int s=0;s<cn;s++) if(mask & (1<<s)) cost[mask]=min(cost[mask],dist[0][from[s]]+DP[s][mask]); } cost[0]=0; int ans=(1<<28); for(int mask=0;mask<(1<<cn);mask++) ans=min(ans,max(cost[mask],cost[(1<<cn)-1-mask])); //諸悪の根源 return ans; } };
SRM411 div2 hard
· 元記事
最大でも200*200程度の大きさでしかないので
座標を全てメモリにとってシュミレーション。
#include <vector> #include <queue> #define OFFSET 100 using namespace std; class HoleCakeCuts { public: int cake[201][201]; int cutTheCake(int cakeLength, int holeLength, vector <int> horizontalCuts, vector <int> verticalCuts) { for(int x=-100;x<=100;x++) for(int y=-100;y<=100;y++) cake[x+OFFSET][y+OFFSET]=-2; for(int x=-cakeLength;x<cakeLength;x++) for(int y=-cakeLength;y<cakeLength;y++) cake[x+OFFSET][y+OFFSET]=0; for(int x=-holeLength;x<holeLength;x++) for(int y=-holeLength;y<holeLength;y++) cake[x+OFFSET][y+OFFSET]=-2; for(int i=0;i<horizontalCuts.size();i++) for(int x=-100;x<=100;x++) for(int y=-100;y<=100;y++) if(cake[x+OFFSET][y+OFFSET]!=-2 && horizontalCuts[i]<=y) cake[x+OFFSET][y+OFFSET]++; for(int i=0;i<verticalCuts.size();i++) for(int x=-100;x<=100;x++) for(int y=-100;y<=100;y++) if(cake[x+OFFSET][y+OFFSET]!=-2 && verticalCuts[i]<=x) cake[x+OFFSET][y+OFFSET]++; /*for(int y=-10;y<=10;y++) { for(int x=-10;x<=10;x++) printf("%3d",cake[x+OFFSET][y+OFFSET]); printf("\n"); } printf("\n");*/ int ans=0; for(int sx=0;sx<=200;sx++) for(int sy=0;sy<=200;sy++) if(cake[sx][sy]!=-2) { ans++; int color=cake[sx][sy]; int mv[4][2]={{0,-1},{0,1},{-1,0},{1,0}}; queue<int> que; que.push(sx*300+sy); while(!que.empty()) { int x=que.front()/300; int y=que.front()%300; que.pop(); if(x<0 || 200<x || y<0 || 200<y) continue; if(cake[x][y]!=color) continue; cake[x][y]=-2; for(int i=0;i<4;i++) { int xx=x+mv[i][0]; int yy=y+mv[i][1]; que.push(xx*300+yy); } } } return ans; } };
SRM309 div2 hard
· 元記事
候補になりそうな所を全部試したら通った
#include <algorithm> #include <climits> #include <sstream> #include <string> #include <vector> typedef long long LL; using namespace std; LL ABS(LL n) { return (0<=n) ? n : -n; } class SynchronizingGuideposts { public: LL minCost(vector<string> _guideposts,int C) { int n=_guideposts.size(); vector<pair<LL,LL> > guide(n); vector<LL> candiate; candiate.push_back(0); for(int i=0;i<n;i++) { stringstream ss(_guideposts[i]); LL a,b,c; ss >> a >> b >> c; guide[i].first=a+b; guide[i].second=c; if(0<=a+b-c) candiate.push_back(a+b-c); if(0<=a+b) candiate.push_back(a+b); if(0<=a+b+c) candiate.push_back(a+b+c); } LL ans=LLONG_MAX; for(int ci=0;ci<candiate.size();ci++) { LL t=0; LL p=candiate[ci]; for(int i=0;i<n;i++) { LL d=ABS(p-guide[i].first); t+=d; if(guide[i].second<d) t+=(d-guide[i].second)*(LL)C; } ans=min(ans,t); } return ans; } };
SRM434 div2 hard
· 元記事
多倍長計算が必要、最初はdoubleでやろうとしたけど通らなかったので(解説見たので)多倍長に切り替えた。5時間ぐらいかかったんちゃうかな。
#include <string> #include <functional> #include <algorithm> #include <vector> using namespace std; int c2i(char c) { return (c<='9') ? c-'0' : c-'A'+10; } char i2c(int i) { return (i<=9) ? (char)(i+'0') : (char)(i-10+'A'); } string add(string a,string b) { string r(60,0); int carry=0; for(int i=59;0<=i;i--) { int cur=a[i]+b[i]+carry; carry=cur/36; cur%=36; r[i]=cur; } return r; } class HexatridecimalSum { public: string maximizeSum(vector<string> s_numbers,int p) { for(int i=0;i<s_numbers.size();i++) for(int j=0;j<s_numbers[i].size();j++) s_numbers[i][j]=c2i(s_numbers[i][j]); vector<string> numbers=s_numbers; int n=numbers.size(); for(int i=0;i<n;i++) { int m=numbers[i].size(); numbers[i]=string(60-m,0)+numbers[i]; } vector<pair<string,int> > weight(36); for(int i=0;i<36;i++) { vector<string> tmp=s_numbers; for(int j=0;j<n;j++) for(int k=0;k<(int)tmp[j].size();k++) if(tmp[j][k]==i) tmp[j][k]=35; string s(60,0); for(int j=0;j<n;j++) { string t=string(60-tmp[j].size(),0)+tmp[j]; s=add(s,t); } weight[i].first=s; weight[i].second=i; } sort(weight.begin(),weight.end()); reverse(weight.begin(),weight.end()); vector<string> tmp=s_numbers; for(int i=0;i<p;i++) { char c=weight[i].second; for(int j=0;j<n;j++) { for(int l=0;l<(int)tmp[j].size();l++) if(tmp[j][l]==c) tmp[j][l]=35; } } for(int i=0;i<n;i++) numbers[i]=string(60-tmp[i].size(),0)+tmp[i]; string s(60,0); for(int i=0;i<n;i++) s=add(s,numbers[i]); int i=0; while(s[i]==0) i++; string ans; while(i<60) { ans+=i2c(s[i]); i++; } if(ans=="") ans="0"; return ans; } };
SRM326 div2 hard
· 元記事
水面の高さを1ずつ上げていってBFSすると簡単に書けた
#include <queue> #include <vector> #include <string> using namespace std; class PoolFiller { public: int BFS(vector<string> pool) { int h=pool.size(),w=pool[0].size(); int r=0; int mv[4][2]={{0,-1},{0,1},{-1,0},{1,0}}; for(int sy=0;sy<h;sy++) for(int sx=0;sx<w;sx++) if(pool[sy][sx]=='.') { int extent=0; bool isSurrounded=true; queue<int> que; que.push(sy*100+sx); while(!que.empty()) { int y=que.front()/100,x=que.front()%100; que.pop(); if(y<0 || h<=y || x<0 || w<=x) { isSurrounded=false; continue; } if(pool[y][x]=='#') continue; pool[y][x]='#'; extent++; for(int i=0;i<4;i++) { int yy=y+mv[i][0],xx=x+mv[i][1]; que.push(yy*100+xx); } } if(isSurrounded) r+=extent; } return r; } int getCapacity(vector<string> layout) { int ans=0; int h=layout.size(),w=layout[0].size(); for(char water='2';water<='9';water++) { vector<string> pool(h,string(w,'#')); for(int i=0;i<h;i++) for(int j=0;j<w;j++) if(layout[i][j] < water) pool[i][j]='.'; ans+=BFS(pool); } return ans; } };
SRM464 div2 hard
· 元記事
拡張幅優先探索って言うのかな。知らないけど。
prob[y][x][visited]でBFSすると通る。
ビット演算で間違えた。
#include <string> #include <queue> #include <vector> using namespace std; class Q { public: int x,y,visited; double safe; }; class ColorfulMazeTwo { public: double prob[50][50][(1<<7)]; double getProbability(vector<string> maze,vector<int> trap) { int h=maze.size(),w=maze[0].size(); int sx=0,sy=0,ey=0,ex=0; for(int i=0;i<h;i++) for(int j=0;j<w;j++) { for(int k=0;k<(1<<7);k++) prob[i][j][k]=0.0; if(maze[i][j]=='$') sy=i,sx=j; if(maze[i][j]=='!') ey=i,ex=j; } queue<Q> que; Q q; q.y=sy,q.x=sx,q.visited=0,q.safe=1.0; que.push(q); while(!que.empty()) { int x=que.front().x,y=que.front().y,visited=que.front().visited; double safe=que.front().safe; que.pop(); if(x<0 || w<=x || y<0 || h<=y) continue; if(maze[y][x]=='#') continue; if('A'<=maze[y][x] && maze[y][x]<='G') { int m=maze[y][x]-'A'; if((visited & (1<<m))==0) { double d=(double)(100-trap[m])/100.0; safe*=d; visited = (visited | (1<<m)); } } if(prob[y][x][visited] < safe) { prob[y][x][visited]=safe; int mv[4][2]={{0,-1},{0,1},{-1,0},{1,0}}; for(int i=0;i<4;i++) { int yy=y+mv[i][0],xx=x+mv[i][1]; q.y=yy,q.x=xx,q.visited=visited,q.safe=safe; que.push(q); } //printf("x=%d y=%d visited=%d safe=%lf\n",x,y,visited,safe); } } double ans=0.0; for(int i=0;i<(1<<7);i++) ans=max(ans,prob[ey][ex][i]); return ans; } };
SRM330 div2 hard
· 元記事
久しぶりにパズル的な問題が出たので手も足も出なかった
(前半分 + 前半分をひっくり返したもの)もしくは
(前半分+1 + 前半分+1をひっくり返したもの)が答え
#include <algorithm> #include <string> #include <vector> using namespace std; class NextPalindromicNumber { public: string getNext(string num) { int n=num.size(); if(count(num.begin(),num.end(),'9')==n) return "1"+string(n-1,'0')+"1"; string ans(n,'#'); for(int i=n-1;0<=i;i--) ans[i]=ans[n-1-i]=num[i]; if(ans<=num) { bool b=true; for(int i=(n-1)/2;0<=i;i--) if(b) { if(ans[i]<'9') { ans[i]++; ans[n-1-i]=ans[i]; b=false; } else ans[n-1-i]=ans[i]='0'; } } return ans; } };
SRM389 div2 hard
· 元記事
全探索で間に合う、難しさの計算も2^6通り全て試してよい
#include <string> #include <sstream> #include <vector> using namespace std; class GuitarChords { public: vector<int> strings; vector<int> chord; vector<int> play; int sN,cN,ans,all; int s2i(string s) { if(s=="A") return 0; if(s=="A#") return 1; if(s=="B") return 2; if(s=="C") return 3; if(s=="C#") return 4; if(s=="D") return 5; if(s=="D#") return 6; if(s=="E") return 7; if(s=="F") return 8; if(s=="F#") return 9; if(s=="G") return 10; if(s=="G#") return 11; else return -1; } int difficulty() { int mask=0; for(int i=0;i<sN;i++) mask=(mask | (1<<play[i])); if(mask!=all) return (1<<28); vector<int> fred; for(int i=0;i<sN;i++) { int a=play[i]-strings[i]; if(a!=0) fred.push_back(a%12); } if(fred.size()==0) return 0; int r=(1<<28); int fN=fred.size(); for(mask=0;mask<(1<<fN);mask++) { int t=0; for(int i=0;i<fN;i++) for(int j=i+1;j<fN;j++) { int a=(mask & (1<<i)) ? fred[i]+12 : fred[i]; int b=(mask & (1<<j)) ? fred[j]+12 : fred[j]; if(a<b) swap(a,b); t=max(t,a-b); } t++; r=min(t,r); } return r; } void DFS(int index) { if(index==sN) { ans=min(ans,difficulty()); return; } for(int i=0;i<cN;i++) { play[index]=chord[i]; DFS(index+1); } return; } int stretch(vector<string> _strings,vector<string> _chord) { sN=_strings.size(); cN=_chord.size(); strings=vector<int>(sN); chord=vector<int>(cN); play=vector<int>(sN); for(int i=0;i<sN;i++) strings[i]=s2i(_strings[i]); all=0; for(int i=0;i<cN;i++) { chord[i]=s2i(_chord[i]); all=(all | (1<<chord[i])); } ans=(1<<28); DFS(0); return ans; } };
SRM384 div2 hard
· 元記事
また落とした。もう眠いので解説は
TopCoder Statistics
ここ読んでください
#include <cstdio> #include <string> #include <algorithm> using namespace std; class PowerGame { public: string winner(int size0, int size1) { int DP[10005]; for(int num=0;num<10005;num++) { if(num==0) DP[num]=0; else { int w=10005,l=-1; for(int i=1;i*i<=num;i++) { if(DP[num-i*i]%2==0) w=min(w,DP[num-i*i]+1); else l=max(l,DP[num-i*i]+1); } if(w!=10005) DP[num]=w; else DP[num]=l; } } int r=min(DP[size0],DP[size1]); char s[100]; string ans; if(r%2==1) sprintf(s,"Alan will win after %d moves",r); else sprintf(s,"Bob will win after %d moves",r); ans=s; return ans; } };
無題
· 元記事
合宿が中止になったみたいです。そもそも関係ないけど。
SRM329 div2 hard
· 元記事
日付が代わりそうなんだけど…
メモ化再起で書いてしまった。通ったからいいけど何とかしないと。
汚いのであんまり参考にならないと思う。
#include <string> #include <sstream> #include <vector> #include <map> using namespace std; class ProbabilisticTranslator { public: int n; map<pair<int,string>,int> cache; vector<string> text; map<string,vector<string> > dictionary; map<string,int> frequencies; int freq(string b,string f) { //printf("freq-s\n"); int r=0; if(frequencies.find(b+" "+f)!=frequencies.end()) r=frequencies[b+" "+f]; //printf("freq-e\n"); return r; } int rec(int time,string before) { //printf("rec-s\n"); int r; pair<int,string> p=make_pair(time,before); if(cache.find(p)!=cache.end()) { r=cache[p]; } else { if(time==n) { r=0; } else { //printf("rec1-s\n"); vector<string> v=dictionary[text[time]]; //printf("rec1-2\n"); r=0; string now; for(int j=0;j<v.size();j++) { now=v[j]; r=max(r,freq(before,now)+rec(time+1,now)); } //printf("rec1-e\n"); } } //printf("rec-e\n"); return cache[p]=r; } int maximumFidelity(vector<string> _txt,vector<string> _dict,vector<string> _freq) { for(int i=0;i<_txt.size();i++) { stringstream ss(_txt[i]); string s; while(ss >> s) text.push_back(s); } n=text.size(); for(int i=0;i<_dict.size();i++) { stringstream ss(_dict[i]); string s,t; ss >> s; ss >> t; while(ss >> t) dictionary[s].push_back(t); } for(int i=0;i<_freq.size();i++) { stringstream ss(_freq[i]); string s,t; int a; ss >> s >> t; ss >> a; frequencies[s+" "+t]=a; } return rec(0,"###"); } };
SRM353 div2 hard
· 元記事
メモ化再起で通ると思ったらEPSでつまずいた
実数型の統合付き比較は
"x<=y"ではなく"x
#include <cstring> #include <cmath> #include <sstream> #include <string> #include <vector> #define EPS 1e-9 using namespace std; class PlatformJumper { public: int n,v,g; vector<int> x; vector<int> y; vector<int> coin; int cache[50]; bool isCan(int from,int to) { double yDif=y[from]-y[to]; double xDif=abs(x[from]-x[to]); if(yDif<0+EPS) return false; double t=sqrt(2.0*yDif/g); return (xDif<v*t+EPS); } int rec(int s) { int &r=cache[s]; if(r!=-1) ; else { r=0; for(int i=0;i<n;i++) if(isCan(s,i)) r=max(r,rec(i)); r+=coin[s]; } return r; } int maxCoins(vector<string> platforms,int _v, int _g) { v=_v; g=_g; n=platforms.size(); for(int i=0;i<n;i++) { stringstream ss(platforms[i]); int xx,yy,cc; ss >> xx >> yy >> cc; x.push_back(xx); y.push_back(yy); coin.push_back(cc); } memset(cache,-1,sizeof(cache)); int ans=0; for(int i=0;i<n;i++) ans=max(ans,rec(i)); return ans; } };
SRM352 div2 hard
· 元記事
x+yP>=minimum
(xはどの馬も勝たなかったときに入ってくるお金の期待値
yはどの馬かが勝ったときに出て行くお金の期待値(マイナスになる))
を変形して
P=(minimum-x)y
(ただしy==0かつminimum<=xなら return -2;y==0かつx
#include <vector> #define EPS 1e-16 using namespace std; class RaceManagement { public: int popCount(int mask) { int r=0; for(int i=0;i<25;i++) if(mask & (1<<i)) r++; return r; } double getPayoutFactor(vector<int> _p,vector<int> _a,int _m) { int n=_p.size(); double minimum=_m; vector<double> probability,amount; for(int i=0;i<n;i++) { probability.push_back((double)_p[i]/100.0); amount.push_back((double)_a[i]); } double x=0.0,y=0.0; for(int mask=0;mask<(1<<n);mask++) { double p=1.0; for(int i=0;i<n;i++) if((1<<i) & mask) p*=probability[i]; else p*=(1-probability[i]); if(popCount(mask)==1) { for(int i=0;i<n;i++) if((1<<i) & mask) y-=(p*amount[i]); else x+=(p*amount[i]); } else { for(int i=0;i<n;i++) x+=(p*amount[i]); } } //printf("x=%lf y=%lf\n",x,y); if(0.0-EPS<y && y<0.0+EPS) if(minimum-EPS<x) return -2; else return -1; double ans=(minimum-x)/y; if(ans<0.0) return -1; return ans; } };
SRM314 div2 hard
· 元記事
方針が間違ってて何度もTLEした。
cache[すでに使ったフェンス(bit)]でメモれば一発。
#include <cmath> #include <vector> #include <algorithm> using namespace std; class GrasslandFencer { public: vector<int> fences; double cache[(1<<16)]; int n; double heron(int _a,int _b,int _c) { int d[3]; d[0]=_a,d[1]=_b,d[2]=_c; sort(d,d+3); if(d[0]+d[1]<=d[2]) return 0; double a=_a,b=_b,c=_c; double s=(a+b+c)/2.0; return sqrt(s*(s-a)*(s-b)*(s-c)); } int popCount(int mask) { int r=0; for(int i=0;i<20;i++) if(mask & (1<<i)) r++; return r; } double rec(int mask) { //printf("%d\n",popCount(mask)); double &r=cache[mask]; if(r!=-1) { ; } else if(popCount(mask)<3) { r=0.0; } else { for(int i=0;i<n;i++) if(mask & (1<<i)) for(int j=i+1;j<n;j++) if(mask & (1<<j)) for(int k=j+1;k<n;k++) if(mask & (1<<k)) { int m=(mask & (~(1<<i)) & (~(1<<j)) & (~(1<<k))); r=max(r,heron(fences[i],fences[j],fences[k])+rec(m)); } } return r; } double maximalFencedArea(vector<int> _fences) { fences=_fences; n=fences.size(); for(int i=0;i<(1<<n);i++) cache[i]=-1; return rec((1<<n)-1); } };
SRM363 div2 hard
· 元記事
メモ化再起で書いた。n=15に脊髄反射でbitDP的な感じ。
cache[何回目][すでにインストールされているもの(bit)]でメモ化。
#include <string> #include <sstream> #include <vector> using namespace std; class CrazyComponents { public: double cache[55][(1<<15)]; vector<int> require[15]; vector<double> income; vector<double> expense; int k; int n; double rec(int time,int state) { double &r=cache[time][state]; if(r!=-1.0) ; else if(time==k) r=0.0; else { r=0.0; for(int i=0;i<n;i++) { double d=0.0; bool b=true; for(int j=0;j<require[i].size();j++) if( !((1<<require[i][j]) & state) ) b=false; d=rec(time+1,state)/(double)n; if(b) d=max(d,(rec(time+1,(state | (1<<i)))+income[i]-expense[i])/(double)n ); r+=d; } } return r; } double expectedProfit(int _k,vector<string> components, vector <int> _income, vector <int> _expense) { k=_k; n=_income.size(); for(int i=0;i<n;i++) { income.push_back((double)_income[i]); expense.push_back((double)_expense[i]); } for(int i=0;i<n;i++) { stringstream ss(components[i]); int a; while(ss >> a) require[i].push_back(a); } for(int i=0;i<=k;i++) for(int j=0;j<(1<<n);j++) cache[i][j]=-1.0; return rec(0,0); } };
SRM366 div2 hard
· 元記事
最初に選ぶ所を決めたら、1回選ぶごとに右と左に分けて考えられる
ターン制の問題は実装が多くなるから嫌い
たぶんDPでもできる。
ていうかこのコードはちょっと頭が悪いので計算量が2倍になっている
#include <cstring> #include <cstdio> #include <utility> #include <vector> using namespace std; class PickGuitars { public: vector<int> guiter; pair<int,int> cache[50][50][2]; pair<int,int> rec(int f,int e,bool isPlayer) { pair<int,int> &r=cache[f][e][(isPlayer)?1:0]; if(r.first!=-1) ; else if(f==e) r.first=0,r.second=0; else { if(isPlayer) { r.first=-1; for(int i=f;i<e;i++) { pair<int,int> p,q; p=rec(f,i,false),q=rec(i+1,e,false); if(r.first<p.first+guiter[i]+q.first) { r.first=p.first+guiter[i]+q.first; r.second=p.second+q.second; } } } else { r.second=-1; for(int i=f;i<e;i++) { pair<int,int> p,q; p=rec(f,i,true),q=rec(i+1,e,true); if(r.second<p.second+guiter[i]+q.second) { r.second=p.second+guiter[i]+q.second; r.first=p.first+q.first; } } } } return r; } int maxValue(vector<int> guitarValues) { int n=guitarValues.size(); int ans=0; for(int i=0;i<n;i++) { vector<int> v; for(int j=i+1;j<n;j++) v.push_back(guitarValues[j]); for(int j=0;j<i;j++) v.push_back(guitarValues[j]); guiter=v; memset(cache,-1,sizeof(cache)); ans=max(ans,guitarValues[i]+rec(0,n-1,false).first); } return ans; } };
SRM300 div2 hard
· 元記事
列車を動かしても本質的な(円にそって回すと)順番は変わらないので
貪欲に動かせる列車の中で一番番号の若いものから動かす
ちなみにこれがdiv2hard50問め、あと50問
#include <string> using namespace std; class CircleOrder { public: string firstOrder(string circle) { circle+=circle; int n=circle.size(); string ans; int a=0; for(int i=0;i<n;i++) if('a'<=circle[i] && circle[i]<='z') a++; a/=2; while(1) { string canMove; char m='z'+1; for(int i=0;i<n;i++) { if('a'<=circle[i] && circle[i]<='z') { char dist=circle[i]-'a'+'A'; for(int j=i-1;0<=j;j--) { if('a'<=circle[j] && circle[j]<='z') break; if(circle[j]==dist && circle[i]<m) m=circle[i]; } for(int j=i+1;j<n;j++) { if('a'<=circle[j] && circle[j]<='z') break; if(circle[j]==dist && circle[i]<m) m=circle[i]; } } } if(m=='z'+1) break; ans.push_back(m); for(int i=0;i<n;i++) { if(circle[i]==m) circle[i]='#'; if(circle[i]==m-'a'+'A') circle[i]-=('A'-'a'); } } return ((int)ans.size()==a) ? ans : "NONE"; } };
SRM315 div2 hard
· 元記事
1時間分からなかったので解説見てしまった。
悔しいけどエジソンが言ってる言葉もあるし次頑張る。
ソースコードは解説の丸パクなのでそっちを見てください。
TopCoder Statistics
シュミレーションを早めに見切れば思いついたかもなあ・・・
SRM439 div2 hard
· 元記事
swapは一番最初にすれば全部試してしまえば良いというのに気づけば一発
一回の操作ごとに前1つか、後ろ1か、前後ろ1つずつ考えなくても良くなる
#include <cstring> #include <string> using namespace std; class PalindromeFactory { public: string source[50][50]; int cache[50][50][50][50]; int rec(int s1,int s2,int f,int e) { int &r=cache[s1][s2][f][e]; string s=source[s1][s2]; if(r!=-1) ; else if(e<=f) r=0; else if(s[f]==s[e]) r=rec(s1,s2,f+1,e-1); else { int a=1+rec(s1,s2,f,e-1); int b=1+rec(s1,s2,f+1,e); int c=1+rec(s1,s2,f+1,e-1); r=min(a,min(b,c)); } return r; } int getEditDistance(string initial) { int n=initial.size(); int ans=50; memset(cache,-1,sizeof(cache)); source[0][0]=initial; ans=min(ans,rec(0,0,0,n-1)); for(int i=0;i<n;i++) for(int j=i+1;j<n;j++) { source[i][j]=initial; swap(source[i][j][i],source[i][j][j]); //printf("%s\n",source[i][j].c_str()); ans=min(ans,1+rec(i,j,0,n-1)); } return ans; } };
SRM425 div2 hard
· 元記事
全探索なんだけど、しんどすぎる。
#include <algorithm> #include <string> #include <vector> #include <cmath> #include <set> using namespace std; class PiecesMover { public: int n; vector<int> pos; int DFS(set<int> se,vector<int> p) { //printf("p.size()=%d\n",p.size()); int r=(1<<28); if(p.size()==n) { sort(p.begin(),p.end()); do{ int a=0; for(int i=0;i<n;i++) { int fx,tx,fy,ty; fy=pos[i]/5,fx=pos[i]%5; ty=p[i]/5,tx=p[i]%5; a+=(int)abs(fy-ty)+(int)abs(fx-tx); } r=min(a,r); }while(next_permutation(p.begin(),p.end())); } else { int mv[4][2]={{-1,0},{1,0},{0,-1},{0,1}}; set<int>::iterator itr; for(itr=se.begin();itr!=se.end();itr++) if(find(p.begin(),p.end(),(*itr)) == p.end()) { vector<int> q=p; q.push_back((*itr)); set<int> te=se; int y=(*itr)/5,x=(*itr)%5; for(int i=0;i<4;i++) { int yy=y+mv[i][0],xx=x+mv[i][1]; if(0<=yy && yy<5 && 0<=xx && xx<5) te.insert(yy*5+xx); } r=min(r,DFS(te,q)); } } return r; } int getMinimumMoves(vector<string> board) { n=0; for(int i=0;i<5;i++) for(int j=0;j<5;j++) if(board[i][j]=='*') { pos.push_back(i*5+j); n++; } int ans=(1<<28); for(int i=0;i<25;i++) { int mv[4][2]={{-1,0},{1,0},{0,-1},{0,1}}; set<int> se; vector<int> v; se.insert(i); v.push_back(i); for(int j=0;j<4;j++) { int yy=i/5+mv[j][0],xx=i%5+mv[j][1]; if(0<=yy && yy<5 && 0<=xx && xx<5) se.insert(yy*5+xx); } ans=min(ans,DFS(se,v)); } return ans; } };
SRM451 div2 hard
· 元記事
BFSやるだけなんだけど、何回かTLEした。詳細はソース内のコメントでどうぞ
#include <cmath> #include <string> #include <queue> #include <vector> using namespace std; class Q { public: int y,x,c; }; class PizzaDelivery { public: int cost[50][50]; int deliverAll(vector<string> terrain) { int h=terrain.size(),w=terrain[0].size(); Q q; queue<Q> que; for(int i=0;i<h;i++) for(int j=0;j<w;j++) { cost[i][j]=(1<<25); if(terrain[i][j]=='X') { q.y=i,q.x=j,q.c=0; que.push(q); } } int mv[4][2]={{0,-1},{0,1},{-1,0},{1,0}}; while(!que.empty()) { int y=que.front().y; int x=que.front().x; int c=que.front().c; que.pop(); if(cost[y][x] <= c) //ここを"cost[y][x] < c"でTLE continue; cost[y][x]=c; for(int i=0;i<4;i++) { int yy=y+mv[i][0],xx=x+mv[i][1],cc=c; if(yy<0 || h<=yy || xx<0 || w<=xx) continue; if(terrain[y][x]=='$' || terrain[yy][xx]=='$') cc+=2; else if(terrain[y][x]=='X' || terrain[yy][xx]=='X') cc+=2; else if(terrain[y][x] == terrain[yy][xx]) cc+=1; else if((int)abs(terrain[y][x]-terrain[yy][xx])==1) cc+=3; else continue; q.y=yy,q.x=xx,q.c=cc; que.push(q); } } vector<int> dist; for(int i=0;i<h;i++) for(int j=0;j<w;j++) if(terrain[i][j]=='$') if((1<<25) <= cost[i][j]) return -1; else dist.push_back(cost[i][j]); int n=dist.size(); int ans=(1<<25); for(int mask=0;mask<(1<<n);mask++) { int a=0,b=0,amax=0,bmax=0; for(int i=0;i<n;i++) if(mask & (1<<i)) { a+=dist[i]*2; amax=max(amax,dist[i]); } else { b+=dist[i]*2; bmax=max(bmax,dist[i]); } ans=min(ans,max(a-amax,b-bmax)); } return ans; } };
SRM429 div2 hard
· 元記事
トポロジカルと間違えて1時間以上かかった。
ただのDFSーーでは説明になってないか
とりあえず条件を全て満たすものを一つ見つける
そのあと最小化するために候補全てのgcd(最大公約数)で割る
#include <cstring> #include <string> #include <vector> using namespace std; typedef long long LL; class IngredientProportions { public: int n; int propotion[10][10]; vector<LL> pre; LL gcd(LL x,LL y) { if(y==0) return x; else return gcd(y,x%y); } void DFS(int s) { for(int i=0;i<n;i++) if(pre[i]==-1 && propotion[s][i]!=-1) { pre[i]=pre[s]/propotion[s][i]*propotion[i][s]; DFS(i); } } vector <int> getMasses(vector<string> input) { n=0; memset(propotion,-1,sizeof(propotion)); pre=vector<LL>(10,-1); LL m=1; for(int i=0;i<(int)input.size();i++) { int a,b,p,q; a=input[i][1]-'0'; b=input[i][8]-'0'; p=input[i][13]-'0'; q=input[i][15]-'0'; n=max(n,max(a+1,b+1)); m*=(p*q/gcd(p,q)/gcd(p,q)); propotion[a][b]=p/gcd(p,q); propotion[b][a]=q/gcd(p,q); } pre[0]=m; DFS(0); LL g=m; for(int i=0;i<n;i++) g=gcd(g,pre[i]); vector<int> ans(n); for(int i=0;i<n;i++) ans[i]=pre[i]/g; return ans; } };
SRM421 div2 hard
· 元記事
親切に0から9まで載っていたから助かった
っていうかこれないと無理
#include <string> #include <vector> using namespace std; class FloorIndicator { public: vector<int> canDig(vector<string> dig,vector<string> ind,int pos) { vector<int> r; for(int n=0;n<10;n++) { bool b=true; for(int i=0;i<5;i++) for(int j=0;j<3;j++) if(ind[i][4*pos+j]=='#' && dig[i][4*n+j]=='.') b=false; if(b) r.push_back(n); } return r; } double averageFloor(int N, vector <string> indicator) { vector<string> digits; digits.push_back("###...#.###.###.#.#.###.###.###.###.###"); digits.push_back("#.#...#...#...#.#.#.#...#.....#.#.#.#.#"); digits.push_back("#.#...#.###.###.###.###.###...#.###.###"); digits.push_back("#.#...#.#.....#...#...#.#.#...#.#.#...#"); digits.push_back("###...#.###.###...#.###.###...#.###.###"); double ans=0; for(int pos=1,t=1;pos<=N;pos++,t*=10) { vector<int> v=canDig(digits,indicator,N-pos); if(v.size()==0) return -1.0; double ave=0; for(int i=0;i<(int)v.size();i++) ave+=v[i]; ave/=(double)v.size(); ans+=ave*(double)t; } return ans; } };
SRM449 div2 hard
· 元記事
DPだと思って頑張ったのに分からないから、解説見たら全探索だった
#include <algorithm> #include <cmath> #include <cstdio> #include <vector> #define OFFSET 3 using namespace std; class HexagonalBattlefieldEasy { public: int field[8][8]; int DFS(int pos) { int x=pos%7,y=pos/7,r=0; if(x==6 && y==6) r = (field[x][y]==1) ? 1 : 0; else if(field[x][y]==1) r = DFS(pos+1); else { field[x][y]=1; if(field[x+1][y]==0) { field[x+1][y]=1; r+=DFS(pos+1); field[x+1][y]=0; } if(field[x+1][y+1]==0) { field[x+1][y+1]=1; r+=DFS(pos+1); field[x+1][y+1]=0; } if(field[x][y+1]==0) { field[x][y+1]=1; r+=DFS(pos+1); field[x][y+1]=0; } field[x][y]=0; } return r; } int countArrangements(vector <int> X, vector <int> Y, int N) { for(int i=0;i<8;i++) for(int j=0;j<8;j++) field[i][j]=1; for(int i=-N+1;i<=0;i++) for(int j=0;j<N;j++) { field[i+OFFSET][i+j+OFFSET]=0; field[i+j+OFFSET][i+OFFSET]=0; } for(int i=N-1;0<=i;i--) for(int j=0;j<N;j++) { field[i+OFFSET][i-j+OFFSET]=0; field[i-j+OFFSET][i+OFFSET]=0; } for(int i=0;i<X.size();i++) field[X[i]+OFFSET][Y[i]+OFFSET]=1; int ans=0; ans=DFS(0); return ans; } }
AOJ 0023
· 元記事
ウォーミングアップその2。2つの円の位置関係は、まあ検索したら出てきます。
誤差が気になったけど通った。
#include <cmath> #include <cstdio> #include <algorithm> int main() { int N; scanf("%d",&N); for(int i=0;i<N;i++) { double xa,ya,ra,xb,yb,rb,d; scanf("%lf%lf%lf%lf%lf%lf",&xa,&ya,&ra,&xb,&yb,&rb); d=sqrt((xa-xb)*(xa-xb)+(ya-yb)*(ya-yb)); if(ra+rb<d) printf("0\n"); else if(ra<rb && d<rb-ra) printf("-2\n"); else if(rb<ra && d<ra-rb) printf("2\n"); else printf("1\n"); } return 0; }
AOJ 0044
· 元記事
やるだけ、頭のウォーミングアップと思ってやった
これを読んでも分からない人は「エラストテネスの篩」で検索
#include <cstdio> #include <cstring> bool isPrime[100000]; int main() { for(int i=0;i<100000;i++) isPrime[i]=true; isPrime[0]=isPrime[1]=false; for(int i=0;i<100000;i++) if(isPrime[i]) for(int j=i+i;j<100000;j+=i) isPrime[j]=false; int input; while(scanf("%d",&input)==1) { for(int i=input-1;0<i;i--) if(isPrime[i]) { printf("%d ",i); break; } for(int i=input+1;i<100000;i++) if(isPrime[i]) { printf("%d\n",i); break; } } return 0; }
SRM305 div2 hard
· 元記事
BFSだった、ミスらないようにゆっくり書いた
最悪の場合間に合わないけど、枝刈りがたくさん起こるので大丈夫
#include <algorithm> #include <queue> using namespace std; class QUE { public: int m,c,b,d; }; class Cannibals { public: int dist[100+10][100+10][2]; int minCrossings(int M, int C, int R) { fill(&dist[0][0][0],&dist[100+9][100+9][1],(1<<28)); QUE q; queue<QUE> que; q.m=M,q.c=C,q.b=0,q.d=0; que.push(q); while(!que.empty()) { int m=que.front().m; int c=que.front().c; int b=que.front().b; int d=que.front().d; que.pop(); //printf("m=%d c=%d b=%d d=%d \n",m,c,b,d); int om=M-m; int oc=C-c; int ob=(b+1)%2; if((c<=m || m==0) && (oc<=om || om==0) && d<dist[m][c][b]) { dist[m][c][b]=d; int bm,bc; for(bm=0;bm<=m;bm++) for(bc=0;bc<=c;bc++) if((c-bc<=m-bm || m-bm==0) && (bc<=bm || bm==0) && 0<bm+bc && bm+bc<=R) { q.m=om+bm,q.c=oc+bc,q.b=ob,q.d=d+1; que.push(q); } } } return dist[M][C][1]==(1<<28) ? -1 : dist[M][C][1]; } };
無題
· 元記事
mozc使いたいけど、面倒くさいのはいや
のっけからLinuxユーザーとしてはあるまじきタイトルだ
ビルトしろよアホ、という声が動物園のペンギンの檻から聞こえてきそう
SRM351 div2 hard
· 元記事
答えみてしもうた。以下は解答の適当日本語訳。
1:選んでやつは消去できると考えられる
2:消去した残りがソートされてればよい
3:選ぶものの合計を最小化したい
4:残されるものの最大化
5:よって、最長増加部分列
#include <algorithm> #include <vector> using namespace std; class InsertSort { public: int calcMinimalCost(vector<int> theArray) { int DP[50]; int n=theArray.size(); int a=0; for(int i=0;i<n;i++) { DP[i]=theArray[i]; for(int j=0;j<i;j++) if(theArray[j]<=theArray[i]) DP[i]=max(DP[i],DP[j]+theArray[i]); a=max(a,DP[i]); } int ans=0; for(int i=0;i<n;i++) ans+=theArray[i]; ans-=a; return ans; } };
SRM302 div2 hard
· 元記事
div2hardの40問目。
メモ化再帰で解いた。ほとんど有り得ないけど、最悪O(100000*√100000)。
解説見るとBFS出来るらしい。値が減ることがないから。
#include <algorithm> using namespace std; int cache[100000+1]; class DivisorInc { public: int M; int rec(int num) { if(M<num) return (1<<28); if(cache[num]!=-1) return cache[num]; cache[num]=(1<<28); for(int i=2;i*i<=num;i++) if(num%i==0) { cache[num]=min(cache[num],1+rec(num+i)); cache[num]=min(cache[num],1+rec(num+num/i)); } return cache[num]; } int countOperations(int N, int _M) { M=_M; fill(&cache[0],&cache[100000],-1); cache[M]=0; int ans=rec(N); ans=((1<<28)<=ans) ? -1 : ans; return ans; } };
無題
· 元記事
東京バンドワゴンを読んでます。まだ評価はできないけどまあまあ面白いんじゃないかな
SRM388 div2 hard
· 元記事
数学。写した。
#include <vector> #include <algorithm> #include <list> using namespace std; class SmoothNumbersHard { public: int countSmoothNumbers(int n, int k) { vector<int> p(n+1,0); for(int i=2;i<=n;i++) if(!p[i]) for(int j=i;j<=n;j+=i) p[j]=i; int ans=0; for(int i=1;i<=n;i++) if(p[i]<=k) ans++; return ans; } };
SRM356 div2 hard
· 元記事
ハイパー実装ゲー。解法は最小コスト木なのでプリム法。
#include <algorithm> #include <set> #include <string> #include <sstream> #include <queue> #include <vector> using namespace std; class UNIONFIND { public: vector<int> data; UNIONFIND(int size) { for(int i=0;i<size;i++) data.push_back(i); } int root(int a) { if(data[a]==a) return data[a]; else return data[a]=root(data[a]); } void set(int a,int b) { if(root(a)!=root(b)) data[root(a)]=data[root(b)]; } }; class BRANCH { public: int left,right,cost; string id; bool operator<(const BRANCH &b) const { if(cost!=b.cost) return (cost>b.cost); else return (id>b.id); } }; class RoadReconstruction { public: set<string> cities; int s2i(string s) { return (int)distance(cities.begin(),cities.find(s)); } string selectReconstruction(vector<string> roads) { for(int i=0;i<roads.size();i++) { stringstream ss(roads[i]); string id,c1,c2; ss >> id >> c1 >> c2; cities.insert(c1); cities.insert(c2); } int n=cities.size(); UNIONFIND uni(n); priority_queue<BRANCH> que; for(int i=0;i<roads.size();i++) { stringstream ss(roads[i]); string id,c1,c2; ss >> id >> c1 >> c2; int c; if(ss >> c) { BRANCH b; b.left=s2i(c1),b.right=s2i(c2),b.cost=c,b.id=id; que.push(b); } else { uni.set(s2i(c1),s2i(c2)); } } vector<string> vs; while(!que.empty()) { BRANCH b=que.top(); que.pop(); if(uni.root(b.left)!=uni.root(b.right)) { vs.push_back(b.id); uni.set(b.left,b.right); } } int chech=uni.root(0); for(int i=0;i<n;i++) if(uni.root(i)!=chech) return "IMPOSSIBLE"; sort(vs.begin(),vs.end()); string ans; for(int i=0;i<vs.size();i++) { if(i!=0) ans+=" "; ans+=vs[i]; } return ans; } };
SRM364 div2 hard
· 元記事
n<=16とかビットですよって言ってるようなもの
#include <algorithm> #include <string> #include <vector> using namespace std; class PowerPlants { public: int n,aim; int cache[(1<<16)]; vector<string> cost; int popCount(int num) { int r=0; for(int i=0;i<n;i++) if(num & (1<<i)) r++; return r; } int rec(int state) { int &r=cache[state]; if(r!=-1) return r; if(aim <= popCount(state)) return r=0; r=(1<<28); for(int i=0;i<n;i++) { if(state & (1<<i)) continue; for(int j=0;j<n;j++) if(state & (1<<j)) r=min(r,cost[j][i]+rec( (state | (1<<i)) )); } return r; } int minCost(vector<string> _cost,string active,int _aim) { cost=_cost; n=cost.size(); for(int i=0;i<n;i++) for(int j=0;j<n;j++) { char c=cost[i][j]; if('0'<=c && c<='9') c-='0'; else if('A'<=c && c<='Z') { c-='A'; c+=10; } cost[i][j]=c; } aim=_aim; fill(&cache[0],&cache[(1<<n)],-1); int state=0; for(int i=0;i<n;i++) if(active[i]=='Y') state=( state | (1<<i) ); return rec(state); } };
SRM383 div2 hard
· 元記事
行きと帰りでダイクストラして足すだけ、実装がエラいことになった
#include <string> #include <vector> #include <queue> #include <algorithm> #include <cmath> using namespace std; class EDGE { public: int to,cost; bool operator <(const EDGE &e) const { return !(cost<e.cost); } }; class HillWalker { public: int h,w,limit,thres; vector<string> map; int dist(int a,int b) { int y1=a/w,x1=a%w,y2=b/w,x2=b%w; if(thres < abs(map[y1][x1]-map[y2][x2])) return (1<<29); if(map[y1][x1] < map[y2][x2]) { int i=map[y1][x1]-map[y2][x2]; i*=i; return i; } else return 1; } int highestPoint(vector<string> _map,int _thres,int _limit) { map=_map; thres=_thres; limit=_limit; h=map.size(); w=map[0].size(); for(int i=0;i<h;i++) for(int j=0;j<w;j++) { char c=map[i][j]; if('A'<=c && c<='Z') c-='A'; else if('a'<=c && c<='z') { c-='a'; c+=26; } map[i][j]=c; } /*printf("map\n"); for(int i=0;i<h;i++) { for(int j=0;j<w;j++) printf("%4d",map[i][j]); printf("\n"); }*/ vector<vector<int> > toCost(h,vector<int>(w,(1<<29))); vector<vector<int> > fromCost(h,vector<int>(w,(1<<29))); int mv[4][2]={{-1,0},{1,0},{0,-1},{0,1}}; EDGE e; priority_queue<EDGE> que; e.cost=0,e.to=0; que.push(e); while(!que.empty()) { e=que.top(); que.pop(); int y=e.to/w,x=e.to%w,c=e.cost; if(c < toCost[y][x]) { toCost[y][x]=c; for(int i=0;i<4;i++) { int yy=y+mv[i][0],xx=x+mv[i][1]; if(0<=yy && yy<h && 0<=xx && xx<w) { e.to=yy*w+xx; e.cost=toCost[y][x]+dist(y*w+x,yy*w+xx); que.push(e); } } } } /*printf("toCost\n"); for(int i=0;i<h;i++) { for(int j=0;j<w;j++) printf("%4d",toCost[i][j]); printf("\n"); }*/ e.cost=0,e.to=0; que.push(e); while(!que.empty()) { e=que.top(); que.pop(); int y=e.to/w,x=e.to%w,c=e.cost; if(c < fromCost[y][x]) { fromCost[y][x]=c; for(int i=0;i<4;i++) { int yy=y+mv[i][0],xx=x+mv[i][1]; if(0<=yy && yy<h && 0<=xx && xx<w) { e.to=yy*w+xx; e.cost=fromCost[y][x]+dist(yy*w+xx,y*w+x); que.push(e); } } } } /*printf("fromCost\n"); for(int i=0;i<h;i++) { for(int j=0;j<w;j++) printf("%4d",fromCost[i][j]); printf("\n"); }*/ int ans=0; for(int i=0;i<h;i++) for(int j=0;j<w;j++) if(toCost[i][j]+fromCost[i][j] <= limit) ans=max(ans,(int)map[i][j]); return ans; } };
SRM469 div2 hard
· 元記事
書くだけなんだけど、バグ連発で270点
#include <algorithm> #include <queue> #include <vector> using namespace std; class TheMoviesLevelThreeDivTwo { public: int n; vector<int> timeJ,timeB; queue<int> qJ,qB; bool check() { int tJ=0,tB=0; int wJ=0,wB=0; while(!qJ.empty() && !qB.empty() && wJ!=n && wB!=n) { if(tJ+timeJ[qJ.front()] < tB+timeB[qB.front()]) { tJ+=timeJ[qJ.front()]; wJ++; qB.push(qJ.front()); qJ.pop(); continue; } if(tB+timeB[qB.front()] < tJ+timeJ[qJ.front()]) { tB+=timeB[qB.front()]; wB++; qJ.push(qB.front()); qB.pop(); continue; } else { tJ+=timeJ[qJ.front()]; tB+=timeB[qB.front()]; wJ++,wB++; qB.push(qJ.front()); qJ.push(qB.front()); qJ.pop(); qB.pop(); continue; } } return (n==wJ || n==wB); } int find(vector<int> _timeJ,vector<int> _timeB) { timeJ=_timeJ; timeB=_timeB; n=timeJ.size(); int ans=0; for(int dis=0;dis<(1<<n);dis++) { while(!qJ.empty()) qJ.pop(); while(!qB.empty()) qB.pop(); for(int i=0;i<n;i++) if(dis & (1<<i)) qJ.push(i); else qB.push(i); if(check()) ans++; } return ans; } };
SRM303 div2 hard
· 元記事
「やるだけ=バグ死」の敗北の方程式を何とかしたい
…で、やるだけ。全探索で間に合う。
#include <string> #include <algorithm> #include <sstream> #include <vector> #include <iostream> using namespace std; class PrimePalindromic { public: int count(int A, int B) { int ans=0; for(int num=A;num<=B;num++) { int n=num; vector<string> vs; while(1<n) { int a; for(a=2;a*a<=n;a++) if(n%a==0) break; if(n<a*a) a=n; stringstream ss; ss << a; vs.push_back(ss.str()); n/=a; } sort(vs.begin(),vs.end()); bool pal=false; do{ string s,t; for(int i=0;i<(int)vs.size();i++) s+=vs[i]; t=s; reverse(t.begin(),t.end()); if(s==t) { pal=true; break; } }while(next_permutation(vs.begin(),vs.end())); if(pal) ans++; } return ans; } };
SRM348 div2 hard
· 元記事
メモ化再帰
"An increasing subsequence of a is maximal if unerasing any of the erased elements of a does not result in a longer increasing subsequence"
ていうのは消したものを追加しても追加前より長い"increasing subsequence"
になることはないよ、という意味らしい
英語の読解に15分くらいかかった
#include <algorithm> #include <vector> typedef long long LL; using namespace std; class IncreasingSubsequences { public: vector<int> a; LL cache[50+1]; int n; LL rec(int last) { LL &r=cache[last]; if(r!=-1) return r; r=0; int m=1000000000+10; for(int i=last+1;i<n;i++) { if(a[last]<a[i] && a[i]<m) { m=a[i]; r+=rec(i); } } if(r==0) r=1; return r; } LL count(vector<int> _a) { a=_a; reverse(a.begin(),a.end()); a.push_back(0); reverse(a.begin(),a.end()); fill(&cache[0],&cache[51],-1); n=a.size(); return rec(0); } };
SRM395 div2 hard
· 元記事
DP[何回目][ボーナスポイント]=得点でDP
O(50^2)でちょっと変だと思ったけど送信した
英語を読み間違えた,"lose that many point"はpointを手に入れられないではなく
pointが引かれる(-point)が足されるという意味
#include <cstring> #include <algorithm> #include <vector> using namespace std; class TriviaGame { public: int DP[50+1][50]; int maximumScore(vector<int> points, int tokensNeeded, vector<int> bonuses) { int n=points.size(); for(int i=0;i<n+1;i++) for(int j=0;j<tokensNeeded;j++) DP[i][j]=-(1<<28); DP[0][0]=0; for(int time=0;time<n;time++) for(int token=0;token<tokensNeeded;token++) if(DP[time][token]!=-(1<<28)) { DP[time+1][token]=max(DP[time+1][token],DP[time][token]-points[time]); if(token+1==tokensNeeded) DP[time+1][0]=max(DP[time+1][0],DP[time][token]+points[time]+bonuses[time]); else DP[time+1][token+1]=max(DP[time+1][token+1],DP[time][token]+points[time]); } int ans=0; for(int i=0;i<tokensNeeded;i++) ans=max(ans,DP[n][i]); return ans; } };
SRM378 div1 easy
· 元記事
asi1024さんが誘ってくれたのでやった
普通にやればいいんだけど、-1となるケースに注意(落とした)
チャレンジやるとは思ってなかったのですぐsysytem testした
asi1024さんすいません
#include <algorithm> #include <vector> using namespace std; class TrueStatements { public: int numberTrue(vector <int> st) { int ans=-1; sort(st.begin(),st.end()); if(st[0]!=0) ans=0; for(int i=0;i<(int)st.size();i++) if( st[i] == (int)distance(lower_bound(st.begin(),st.end(),st[i]), upper_bound(st.begin(),st.end(),st[i])) ) ans=max(ans,st[i]); return ans; } };
SRM318 div2 hard
· 元記事
DPなんだけど、外した場合の事を考えてなくてエラいことになった
DP[何回目][何点]=ここからwinする確立(パーセント)
#include <algorithm> using namespace std; class SimplifiedDarts { public: double DP[1000+1][3000+1]; double tryToWin(int W, int N, int P1, int P2) { for(int time=N;0<=time;time--) for(int point=3000;0<=point;point--) { if(time==N && W<=point) DP[time][point]=100.0; if(time==N && point<W) DP[time][point]=0.0; if(time!=N) { DP[time][point]=0.0; if(point+2<=3000) DP[time][point]=max(DP[time][point],DP[time+1][point+2]*P1/100.0+DP[time+1][point]*(100-P1)/100.0); if(point+3<=3000) DP[time][point]=max(DP[time][point],DP[time+1][point+3]*P2/100.0+DP[time+1][point]*(100-P2)/100.0); } } return DP[0][0]; } };
SRM398 div2 hard
· 元記事
全探索。簡単だった。900で納得。
動かさないやつを決める
#include <string> #include <vector> #include <iostream> #include <cstdio> using namespace std; class MatchString { public: int placeWords(string matchString, vector <string> matchWords) { int len=0; int n=matchWords.size(); for(int i=0;i<n;i++) len=max(len,(int)matchWords[i].size()); for(int i=0;i<n;i++) matchWords[i]+=string(len-matchWords[i].size(),'.'); int ans=(1<<28); for(int anchorY=0;anchorY<n;anchorY++) for(int anchorX=0;anchorX<len;anchorX++) if(matchWords[anchorY][anchorX]==matchString[anchorY]) { int t=0; for(int y=0;y<n;y++) { int l=0; while(matchWords[y][anchorX-l]!=matchString[y] && 0<=anchorX-l) l++; if(anchorX-l<0) l=(1<<28); t+=l; if((1<<28)<=t) break; } ans=min(ans,t); } return (ans==(1<<28)) ? -1 : ans; } };
SRM323 div2 hard
· 元記事
全探索、AとBしか使わないんじゃねと思ったので思い切って書いてみたらあたった
怖かった
実際はもうちょっと文字を使う、ダメだ俺
#include <string> #include <vector> #include <cstring> #include <iostream> using namespace std; class UnrepeatableWords { public: int k,n,allowed; string rec(string before) { //cout << before << endl; if(before.size()==n) return ""; bool NG[allowed]; memset(NG,false,sizeof(NG)); for(int len=1;k*len-1<=before.size();len++) { int pos=before.size()-len*k+1; string s=before.substr(pos,len); int i; for(i=0;i<k-1;i++) if(s!=before.substr(pos+i*len,len)) break; if(i==k-1 && s.substr(0,len-1)==before.substr(pos+len*(k-1),len-1)) NG[s[len-1]-'A']=true; } for(int i=0;i<allowed;i++) if(!NG[i]) { string s; char c=i+'A'; s=c+rec(before+c); if(s.size()==n-before.size()) return s; } return ""; } string getWord(int _k, int _n, int _allowed) { k=_k,n=_n,allowed=_allowed; return rec(""); } };
SRM328 div2 hard
· 元記事
DP苦手だからメモ化再帰
#include <map> #include <string> #include <sstream> #include <vector> #include <cstring> using namespace std; typedef pair<bool,vector<string> > KEY; class ScoreDifference { public: int board[4][4]; int cache[2][(1<<16)]; int ABS(int num) { return num<0 ? -num : num; } bool calc(int s,int y,int x) { int a=(y*4+x); return s & (1<<a); } int rec(bool isPlayer,int canMove) { int &r=cache[(isPlayer) ? 1 : 0][canMove]; if(r!=(1<<28)) return r; if(canMove==(1<<16)-1) return 0; if(isPlayer) r=-(1<<28); else r=(1<<28); for(int y=0;y<4;y++) for(int x=0;x<4;x++) if(!calc(canMove,y,x)) { bool isCan=(y==0 || y==3 || x==0 || x==3); isCan=(isCan || (0<y && calc(canMove,y-1,x)) ); isCan=(isCan || (y<3 && calc(canMove,y+1,x)) ); isCan=(isCan || (0<x && calc(canMove,y,x-1)) ); isCan=(isCan || (x<3 && calc(canMove,y,x+1)) ); if(!isCan) continue; int c=canMove; c=(c | (1<<(y*4+x)) ); if(isPlayer) r=max(r,rec(false,c)+board[y][x]); else r=min(r,rec(true,c)-board[y][x]); } return r; } int maximize(vector <string> input) { for(int i=0;i<4;i++) { stringstream ss(input[i]); for(int j=0;j<4;j++) { int a; ss >> a; board[i][j]=a; printf("%3d",a); } printf("\n"); } for(int i=0;i<2;i++) for(int j=0;j<(1<<16);j++) cache[i][j]=(1<<28); printf("rec(true,0)=%d\n",rec(true,0)); printf("rec(false,0)=%d\n",rec(false,0)); int ans=0; ans=max(ans,rec(true,0)); ans=max(ans,rec(false,0)); return ans; } };
SRM357 div2 hard
· 元記事
ワーシャルフロイドではなかなか通らないので
ダイクストラを使うべし(解説に書いてあった)
ビット演算とかどんだけ
#include <vector> #include <string> #include <cstring> #include <set> #include <sstream> #include <utility> using namespace std; typedef long long LL; class WebsiteRank { public: int n; bool G[1000][1000]; bool H[1000][1000]; vector<LL> weight; set<string> siteToNum; int sToN(string s) { return (int)distance(siteToNum.begin(),siteToNum.find(s)); } LL rec(int index) { LL &r=weight[index]; if(r!=-1) return r; r=1; for(int i=0;i<n;i++) if(G[index][i]) r+=rec(i); return r; } LL countVotes(vector <string> votes, string website) { vector<vector<string> > input(votes.size()); for(int i=0;i<(int)votes.size();i++) { stringstream ss(votes[i]); string s; while(ss >> s) { input[i].push_back(s); siteToNum.insert(s); } } n=siteToNum.size(); memset(G,false,sizeof(G)); memset(H,false,sizeof(H)); for(int i=0;i<(int)votes.size();i++) { string s=input[i][0]; int a=sToN(s); for(int j=1;j<input[i].size();j++) { int b=sToN(input[i][j]); G[a][b]=H[a][b]=true; } } for(int i=0;i<n;i++) G[i][i]=H[i][i]=true; for(int k=0;k<n;k++) for(int i=0;i<n;i++) for(int j=0;j<n;j++) H[i][j]=(H[i][j] || (H[i][k]&&H[k][j]) ); for(int i=0;i<n;i++) for(int j=i;j<n;j++) if(H[i][j] && H[j][i]) G[i][j]=G[j][i]=false; weight=vector<LL>(n,-1); return rec(sToN(website)); } };
SRM360 div2 hard
· 元記事
メモ化再帰ーDPの方がいいと思う
#include <algorithm> #include <vector> #include <cstdio> #include <set> using namespace std; int result[2][1000000+10]; class TakeSubstringGame { public: vector<int> choice(int n) { vector<int> r; int num=n; while(num!=0) { int m=1; while(m<=num) { m*=10; int a=num%m; if(0<a && a<n) r.push_back(a); } num/=10; } return r; } int rec(int player,int num) { int &r=result[player][num]; if(r!=2) return r; r=0; int opo=(player+1)%2; int n=num; while(n!=0) { int m=1; while(m<=n) { m*=10; int a=n%m; if(0<a && a<num && rec(opo,num-a)==0) { r=1; break; } } n/=10; } return r; } int winningMove(int n) { for(int i=0;i<2;i++) for(int j=0;j<=1000000;j++) result[i][j]=2; rec(0,n); vector<int> v=choice(n); sort(v.begin(),v.end()); for(int i=0;i<(int)v.size();i++) if(result[1][n-v[i]]==0) return v[i]; return -1; } };
SRM358 div2 hard
· 元記事
DPした、綺麗に書けた
#include <algorithm> using namespace std; class SameDigits { public: int DP[2][1000+1]; int howMany(int n, int k) { for(int left=0;left<=n;left++) for(int used=0;used<2;used++) { DP[used][left]=0; if(used==1 && left==0) DP[used][left]=1; for(int i=1;i<=min(left,k);i++) { if(i==k) DP[used][left]+=(9*DP[1][left-i]); else DP[used][left]+=(9*DP[used][left-i]); DP[used][left]%=44444444; } } int ans=0; for(int i=1;i<=n;i++) { ans+=DP[0][i]; ans%=44444444; } return ans; } };
JOI本選で大コケしました
· 元記事
1番でグローバル変数じゃないとMLEするのに気づけなかったのが主な敗因
問1ーグローバル変数でとらないとMLE
#include <string> #include <cstdio> #include <cstdlib> #include <vector> #include <iostream> #include <map> using namespace std; typedef long long LL; int memo[1000+1][1000+1][3]; int main() { int h,w; cin >> h >> w; int time; cin >> time; char star[1000][1000]; for(int i=0;i<h;i++) scanf("%s",star[i]); for(int y=0;y<=h;y++) for(int x=0;x<=w;x++) memo[y][x][0]=memo[y][x][1]=memo[y][x][2]=0; for(int y=1;y<=h;y++) for(int x=1;x<=w;x++) { memo[y][x][0]=memo[y][x-1][0]; memo[y][x][1]=memo[y][x-1][1]; memo[y][x][2]=memo[y][x-1][2]; if(star[y-1][x-1]=='J') memo[y][x][0]++; if(star[y-1][x-1]=='O') memo[y][x][1]++; if(star[y-1][x-1]=='I') memo[y][x][2]++; } for(int y=1;y<=h;y++) { for(int x=1;x<=w;x++) { memo[y][x][0]+=memo[y-1][x][0]; memo[y][x][1]+=memo[y-1][x][1]; memo[y][x][2]+=memo[y-1][x][2]; } } for(time;0<time;time--) { int l,r,b,c; cin >> b >> l >> c >> r; int J=0,O=0,I=0; J=(memo[c][r][0]-memo[b-1][r][0]-memo[c][l-1][0]+memo[b-1][l-1][0]); O=(memo[c][r][1]-memo[b-1][r][1]-memo[c][l-1][1]+memo[b-1][l-1][1]); I=(memo[c][r][2]-memo[b-1][r][2]-memo[c][l-1][2]+memo[b-1][l-1][2]); cout << J << " " << O << " " << I << endl; } return 0; }
問2ーメモ化再帰
#include <vector> #include <algorithm> #include <functional> #include <stdio.h> using namespace std; vector<vector<int> > book(10); int cache[10][2000]; int rec(int g,int left) { if(10<=g) return 0; if(cache[g][left]!=-1) return cache[g][left]; int r=0; for(int num=0;num<=left;num++) { if(book[g].size()<num) continue; int a=0; for(int i=0;i<num;i++) a+=(book[g][i]+num-1); r=max(a+rec(g+1,left-num),r); } //printf("cache[%d][%d]=%d\n",g,left,r); return cache[g][left]=r; } int main() { for(int i=0;i<10;i++) for(int j=0;j<2000;j++) cache[i][j]=-1; int N,K; scanf("%d %d",&N,&K); for(int i=0;i<N;i++) { int c,g; scanf("%d %d",&c,&g); g--; book[g].push_back(c); } for(int i=0;i<10;i++) sort(book[i].begin(),book[i].end(),greater<int>()); int ans=rec(0,K); printf("%d\n",ans); return 0; }
問3ーダイクストラだったらしい
#include <iostream> #include <queue> #include <vector> #include <cstdio> using namespace std; class EDGE { public: int to,cost; bool operator<(const EDGE &e) const{ return (cost>e.cost); } }; class BRANCH { public: int left,right,length; }; int main() { int N,M,K; cin >> N >> M >> K; vector<vector<EDGE> > G(N); vector<int> dist(N,(1<<28)); priority_queue<EDGE> que; vector<BRANCH> branch; for(int i=0;i<M;i++) { int a,b,c; cin >> a >> b >> c; a--,b--; EDGE e; e.to=b,e.cost=c; G[a].push_back(e); e.to=a,e.cost=c; G[b].push_back(e); BRANCH br; br.left=a,br.right=b,br.length=c; branch.push_back(br); } for(int i=0;i<K;i++) { int a; cin >> a; a--; EDGE e; e.to=a,e.cost=0; que.push(e); } while(!que.empty()) { int t=que.top().to,c=que.top().cost; que.pop(); if(dist[t]<=c) continue; dist[t]=c; for(int i=0;i<(int)G[t].size();i++) { EDGE e; e.to=G[t][i].to; e.cost=G[t][i].cost+c; que.push(e); } } int ans=0; for(int i=0;i<M;i++) { int a=dist[branch[i].left]+branch[i].length+dist[branch[i].right]; a+=(a%2==1) ? 1 : 0; a/=2; ans=max(ans,a); } cout << ans << endl; }
問4ーソートして真ん中から
#include <algorithm> #include <vector> #include <iostream> #include <utility> #include <cstdio> #define MAX_V 100010 typedef long long LL; using namespace std; LL ABS(LL num) { return (num<0) ? -num : num; } LL xPos[MAX_V]; LL yPos[MAX_V]; pair<LL,LL> house[MAX_V]; int main() { int W,H,N,xN,yN; cin >> W >> H >> N; for(int i=0;i<N;i++) { cin >> xPos[i] >> yPos[i]; house[i]=make_pair(xPos[i],yPos[i]); } sort(xPos,xPos+N); //xN=unique(xPos,xPos+N)-xPos; sort(yPos,yPos+N); //yN=unique(yPos,yPos+N)-yPos; xN=yN=N; //printf("xPos.size()=%d yPos.size()=%d\n",xPos.size(),yPos.size()); vector<LL> xCord; vector<LL> yCord; for(int i=max(0,xN/2-10);i<min(xN,xN/2+10);i++) xCord.push_back(xPos[i]); for(int i=max(0,yN/2-10);i<min(yN,yN/2+10);i++) yCord.push_back(yPos[i]); sort(xCord.begin(),xCord.end()); sort(yCord.begin(),yCord.end()); LL ansX=0; LL ansY=0; LL ansDist=((LL)1<<50); for(int i=0;i<(int)xCord.size();i++) for(int j=0;j<(int)yCord.size();j++) { LL x=xCord[i]; LL y=yCord[j]; LL maxD=0; LL dist=0; //printf("x=%lld y=%lld\n",x,y); for(int k=0;k<N;k++) { LL d=ABS(house[k].first-x)+ABS(house[k].second-y); maxD=max(d,maxD); dist+=d*2; } dist-=maxD; if(dist<ansDist) { ansDist=dist; ansX=x; ansY=y; } } cout << ansDist << endl; cout << ansX << " " << ansY << endl; return 0; }


コメント
yoshiyukiohira
すごい!このくらいの短いコードできるんですね。尊敬します!!
a_kawashiro
敵を全部やっつけた場合の処理を省略したりしているので短くなっているのだと思います。
a_kawashiro
id:ruicc さんのコメントを見て、ちょっと書き直しました
ruicc
一応説明しますと、
map f . map g . map h $ list
の場合Listを3回走査する必要がありますが、
map (f . g . h) list
では1回で済むことになります。
このような関数の適用回数を減らす書き換えを融合変換(fusion transformation)と言いますが、Haskell(GHC)ではこのような最適化を書き換え規則(rewrite rule)を用いることで達成しています。
例えばVector(http://hackage.haskell.org/package/vector-0.10.0.1)を用いると、
map f . map g . map h
と記述しても最適化時にVectorを一回しか走査しない処理に変換してくれます。
map (f. g. h)と手で簡単に書き直せる場合はそうしてもいいですが、modularityを考慮するならば、必要なパーツを好きに組み合わせて目的を達成し、必要な最適化はコンパイラに任せられる様だと嬉しいですね。
map/map以外にも融合変換出来る処理(map/filter, filter/filter、それらを抽象化したfold/build, fold/argument等)あります。以下が参考になると思います。
http://itpro.nikkeibp.co.jp/article/COLUMN/20100112/343099/?ST=develop&P=3
Listに関しても、stream-fusionパッケージ(http://hackage.haskell.org/package/stream-fusion)を用いることで、融合変換してくれるようになります。