週記 2023/09/03-09/09

やまけーさんが書いてるのをみて便乗。

最近1週間が早すぎるので、文章化して本当に1週間生きていたのか確認する。

9/3 (日)

休み。

朝起きてビッグランをした。

銭湯に行こうかと思ったが、AHCが午前10時スタートだと気づいてやめる。とりあえずAHCで正の得点を得た。一瞬2位になったが、秒で抜かされた。

ユーフォのアンサンブルコンテスト編をまだ見てなかったので見に行くことにした。アンコン編は原作を読んだことがあったものの、ほとんど覚えていなかった。劇場はそこそこ人が入っており、意外と年齢層若めで驚いた。

アンコン編は番外編なのでストーリー面での面白さは特になかったが、久石奏と黄前ちゃんが動いているだけで感動。久石奏に関しては全てのカットで動きがあざとすぎて可愛かった。あと、久石奏は黄前ちゃんのこと好きすぎないか?まじで三年生編が楽しみ(原作既読)。麗奈との絡みもよかった。ここも三年生編楽しみ。なかよし川もちらっと登場していて非常によかった。のぞみぞが直接話している描写がないのも趣深い。

久々に原作を読みたくなり本棚を探したが無かった。思い返すと妹にあげたような気がする。仕方がないので電子版で二年生編を購入した。二年生編は映画では描写しきれていないところがあるので、ぜひ原作を読んでほしい。

百鬼あやめの5周年配信を聴きながらAHCをやって寝た。

9/4 (月)

仕事。

早めに切り上げてAHCをしたかったが、21時くらいまで仕事してしまった。無念。

Twitterで弊社がちょっと話題になっていた。あまりにもクソでかカンパニーなため、同じ会社の話という感じがしない。

退勤後、サモランの新ステのトキシラスを遊んだ。AHCをやろうとしたが、眠すぎて断念。

9/5 (火)

仕事。

常用している眼鏡が壊れており、古い眼鏡を付けていたせいか、微妙に頭が痛い。退勤後、AHCをするつもりだったが、大人しく早めに寝た。

9/6 (水)

仕事。

退勤後、3時間くらいAHCをやった。ついでに、以前からVS CodeC++ Intellisence が動いていなかったのを直した。快適になった。得点はいまいち伸びない。全人類強すぎる…

日曜からちょくちょく読んでいた『響け! ユーフォニアム 北宇治高校吹奏楽部、波乱の第二楽章 前編』を読み終えた。前にも読んだことがあったが、読み返しても相変わらず面白い。黄前相談所のくだりが地味に好き。これから後編を読む。アニメも見返したい。

9/7 (木)

仕事。

勢い余って『響け! ユーフォニアム 北宇治高校吹奏楽部、波乱の第二楽章 後編』を読了してしまった。やっぱりみぞれの覚醒シーンが好きすぎる。映画のリズと青い鳥はかなり詩的だったので、カタルシスは小説版の方が感じられると思う。そしてリズと青い鳥がハッピーエンドではないことを再確認した。あれをハッピーエンドと言い張る人とは友達になれない。

AHCは実装量の割にスコアが伸びず苦しい。

9/8 (金)

仕事。

退勤後、楽天ソフトバンクの試合を見ながらAHCをした。6年間仙台に住んでいたこともあり、実は楽天ファン。この日は楽天が勝った。CS行って欲しい。

AHCは相変わらず苦しい。32M台のスコアだが、32M台には3人しかおらず、上と下はもっと人口密度が高い。ハズレ方針を引いている気がするが分からず。

9/9 (土)

休み。

スプラのフェスをやっていたのでちょっとやる。フウカ派。

『響け! ユーフォニアム 北宇治高校吹奏楽部、決意の最終楽章 前編』読了。もともと読んだことはあったが、相変わらず面白い。前に読んだ時は、釜屋つばめとか小日向夢の顔が分からなかったが、アンコン編を履修したおかげで顔が思い浮かぶようになった。ネタバレなんですが久石奏がかわいいです。

後編も購入して一気に読み終えてしまおうかと思ったが、最後まで読み切ってしまうのなんとなく惜しくてやめる。逆に一年生編のアニメを見直すことにした。

AHCを頑張った。いままで書いてたコードを捨てて新しく書き直したところ、37M台にのって一安心。他の大多数の人と大まかな方針は合っていそう。

今週の総括

こいつ、AHCとユーフォの話しかないのか?

ABC239EをGPT-4に解かせた会話ログ

問題URL

問題は適当に選びました。 atcoder.jp

提出URL

9ペナです。 atcoder.jp

会話ログ

You:

問題文 N 頂点の根付き木があります。頂点には 1 から N の番号がついており、根は頂点 1 です。 i 番目の辺は頂点 A i ​ と B i ​ を結んでいます。 頂点 i には整数 X i ​ が書かれています。

Q 個のクエリが与えられます。 i 番目のクエリでは整数の組 (V i ​ ,K i ​ ) が与えられるので、次の問題に答えてください。

問題:頂点 V i ​ の部分木に含まれる頂点に書かれた整数のうち、大きい方から K i ​ 番目の値を求めよ 制約 2≤N≤10 5

0≤X i ​ ≤10 9

1≤A i ​ ,B i ​ ≤N 1≤Q≤10 5

1≤V i ​ ≤N 1≤K i ​ ≤20 与えられるグラフは木である 頂点 V i ​ の部分木は頂点を K i ​ 個以上持つ 入力に含まれる値は全て整数である 入力 入力は以下の形式で標準入力から与えられる。

N Q X 1 ​

… X N ​

A 1 ​

B 1 ​

⋮ A N−1 ​

B N−1 ​

V 1 ​

K 1 ​

⋮ V Q ​

K Q ​

出力 Q 行出力せよ。 i 行目には i 番目のクエリに対する答えを出力せよ。

ChatGPT:

以下は、上記の問題を解決するPythonプログラムの例です。

from collections import defaultdict
from heapq import heappush, heappop

def main():
    n, q = map(int, input().split())
    values = list(map(int, input().split()))

    tree = defaultdict(list)
    for _ in range(n - 1):
        a, b = map(int, input().split())
        tree[a].append(b)
        tree[b].append(a)

    queries = [tuple(map(int, input().split())) for _ in range(q)]

    def dfs(v, parent):
        subtree_values = [values[v - 1]]
        for child in tree[v]:
            if child != parent:
                subtree_values.extend(dfs(child, v))
        return subtree_values

    for v, k in queries:
        subtree_values = dfs(v, 0)
        subtree_values.sort(reverse=True)
        print(subtree_values[k - 1])


if __name__ == "__main__":
    main()

このプログラムは、頂点V_iの部分木に含まれる頂点に書かれた整数のうち、大きい方からK_i番目の値を求める問題を解決します。dfs関数は深さ優先探索を使用して、与えられた頂点の部分木に含まれるすべての値を取得します。その後、クエリごとに部分木の値を降順にソートし、K_i番目の値を出力します。

You:

REが出ました

ChatGPT:

すみません、失礼しました。入力が複数行に分かれていることを考慮していませんでした。以下の修正版をお試しください。

from collections import defaultdict
from heapq import heappush, heappop

def main():
    n, q = map(int, input().split())
    values = list(map(int, input().split()))

    tree = defaultdict(list)
    for _ in range(n - 1):
        a, b = map(int, input().split())
        tree[a].append(b)
        tree[b].append(a)

    queries = [tuple(map(int, input().split())) for _ in range(q)]

    def dfs(v, parent):
        subtree_values = [values[v - 1]]
        for child in tree[v]:
            if child != parent:
                subtree_values.extend(dfs(child, v))
        return subtree_values

    for v, k in queries:
        subtree_values = dfs(v, 0)
        subtree_values.sort(reverse=True)
        if k > len(subtree_values):
            print(-1)
        else:
            print(subtree_values[k - 1])

if __name__ == "__main__":
    main()

修正点:

  • クエリごとに、部分木の値の数がK_iより小さい場合、-1を出力するようにしました。これにより、範囲外のインデックスにアクセスすることがなくなり、REが解決されるはずです。

You:

C++に書き直してもらえますか?

ChatGPT:

もちろんです。以下がC++に書き直したコードです。

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

vector<vector<int>> tree;
vector<int> values;

vector<int> dfs(int v, int parent) {
    vector<int> subtree_values = {values[v - 1]};
    for (int child : tree[v]) {
        if (child != parent) {
            vector<int> child_values = dfs(child, v);
            subtree_values.insert(subtree_values.end(), child_values.begin(), child_values.end());
        }
    }
    return subtree_values;
}

int main() {
    int n, q;
    cin >> n >> q;

    values.resize(n);
    for (int i = 0; i < n; i++) {
        cin >> values[i];
    }

    tree.resize(n + 1);
    for (int i = 0; i < n - 1; i++) {
        int a, b;
        cin >> a >> b;
        tree[a].push_back(b);
        tree[b].push_back(a);
    }

    for (int i = 0; i < q; i++) {
        int v, k;
        cin >> v >> k;
        vector<int> subtree_values = dfs(v, 0);
        sort(subtree_values.rbegin(), subtree_values.rend());

        if (k > subtree_values.size()) {
            cout << -1 << endl;
        } else {
            cout << subtree_values[k - 1] << endl;
        }
    }

    return 0;
}

このC++コードは、Pythonコードと同じロジックを使用しており、頂点V_iの部分木に含まれる頂点に書かれた整数のうち、大きい方からK_i番目の値を求める問題を解決します。dfs関数は深さ優先探索を使用して、与えられた頂点の部分木に含まれるすべての値を取得します。その後、クエリごとに部分木の値を降順にソートし、K_i番目の値を出力します。部分木の値の数がK_iより小さい場合、-1を出力するようにしました。

You:

ジャッジサーバーに提出したところ、Time Limit Exceedとなりました。修正できますか?

ChatGPT:

もちろんです。以下は、C++でより効率的な方法を使用して問題を解決するコードです。

#include <iostream>
#include <vector>
#include <algorithm>
#include <set>

using namespace std;

vector<vector<int>> tree;
vector<int> values;

void dfs(int v, int parent, multiset<int, greater<int>> &subtree_values, int k) {
    subtree_values.insert(values[v - 1]);

    // If the size of the multiset exceeds k, erase the smallest element.
    if (subtree_values.size() > k) {
        subtree_values.erase(prev(subtree_values.end()));
    }

    for (int child : tree[v]) {
        if (child != parent) {
            multiset<int, greater<int>> child_values;
            dfs(child, v, child_values, k);
            for (int value : child_values) {
                subtree_values.insert(value);

                // If the size of the multiset exceeds k, erase the smallest element.
                if (subtree_values.size() > k) {
                    subtree_values.erase(prev(subtree_values.end()));
                }
            }
        }
    }
}

int main() {
    int n, q;
    cin >> n >> q;

    values.resize(n);
    for (int i = 0; i < n; i++) {
        cin >> values[i];
    }

    tree.resize(n + 1);
    for (int i = 0; i < n - 1; i++) {
        int a, b;
        cin >> a >> b;
        tree[a].push_back(b);
        tree[b].push_back(a);
    }

    for (int i = 0; i < q; i++) {
        int v, k;
        cin >> v >> k;
        multiset<int, greater<int>> subtree_values;
        dfs(v, 0, subtree_values, k);
        cout << *next(subtree_values.begin(), k - 1) << endl;
    }

    return 0;
}

このコードは、効率を向上させるために以下の方法を使用しています。

  1. 部分木の値を格納するためにmultisetを使用し、必要なK_i個の最大値だけを維持します。これにより、クエリごとのソートが不要になります。
  2. dfs関数を変更して、親ノードから部分木の値を受け取り、更新するようにしました。これにより、部分木の値を再帰的にマージしていくことができます。

これらの変更により、計算量が削減され、時間制限をクリアできるはずです。

You:

ジャッジサーバーに提出したところ、またしてもTime Limit Exceedが出ました。修正できますか?

ChatGPT:

すみません、再度見直しました。こちらが改善されたC++コードです。このコードでは、dfs関数が部分木の値を引数として直接更新するようになっており、時間制限をクリアできるはずです。

#include <iostream>
#include <vector>
#include <algorithm>
#include <set>

using namespace std;

vector<vector<int>> tree;
vector<int> values;

void dfs(int v, int parent, multiset<int, greater<int>> &subtree_values, int k) {
    subtree_values.insert(values[v - 1]);

    // If the size of the multiset exceeds k, erase the smallest element.
    if (subtree_values.size() > k) {
        subtree_values.erase(prev(subtree_values.end()));
    }

    for (int child : tree[v]) {
        if (child != parent) {
            dfs(child, v, subtree_values, k);
        }
    }
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

    int n, q;
    cin >> n >> q;

    values.resize(n);
    for (int i = 0; i < n; i++) {
        cin >> values[i];
    }

    tree.resize(n + 1);
    for (int i = 0; i < n - 1; i++) {
        int a, b;
        cin >> a >> b;
        tree[a].push_back(b);
        tree[b].push_back(a);
    }

    for (int i = 0; i < q; i++) {
        int v, k;
        cin >> v >> k;
        multiset<int, greater<int>> subtree_values;
        dfs(v, 0, subtree_values, k);
        cout << *next(subtree_values.begin(), k - 1) << endl;
    }

    return 0;
}

このコードでは、C++の高速な入出力処理を有効にするために、ios_base::sync_with_stdio(false)およびcin.tie(NULL)を追加しました。dfs関数の変更により、部分木の値が再帰的にマージされ、計算量が削減されます。これにより、時間制限をクリアできるはずです。

You:

ジャッジサーバーに提出しましたが、やはりTime Limit Exceedです。

ChatGPT:

申し訳ございません。以下のC++コードで効率をさらに向上させ、計算量を削減しています。今回は、dfs関数が部分木の値を返すように変更し、部分木の値を効率的にマージするためにpriority_queueを使用しています。この変更により、時間制限をクリアできるはずです。

#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>

using namespace std;

vector<vector<int>> tree;
vector<int> values;

priority_queue<int, vector<int>, greater<int>> dfs(int v, int parent, int k) {
    priority_queue<int, vector<int>, greater<int>> subtree_values;
    subtree_values.push(values[v - 1]);

    for (int child : tree[v]) {
        if (child != parent) {
            auto child_values = dfs(child, v, k);

            while (!child_values.empty()) {
                subtree_values.push(child_values.top());
                child_values.pop();

                if (subtree_values.size() > k) {
                    subtree_values.pop();
                }
            }
        }
    }

    return subtree_values;
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

    int n, q;
    cin >> n >> q;

    values.resize(n);
    for (int i = 0; i < n; i++) {
        cin >> values[i];
    }

    tree.resize(n + 1);
    for (int i = 0; i < n - 1; i++) {
        int a, b;
        cin >> a >> b;
        tree[a].push_back(b);
        tree[b].push_back(a);
    }

    for (int i = 0; i < q; i++) {
        int v, k;
        cin >> v >> k;
        auto subtree_values = dfs(v, 0, k);
        cout << subtree_values.top() << endl;
    }

    return 0;
}

このコードでは、dfs関数が、頂点V_iの部分木に含まれる頂点に書かれた整数のうち、大きい方からK_i個の値を持つpriority_queueを返すようになっています。この変更により、部分木の値を効率的にマージでき、計算量が削減されます。これにより、時間制限をクリアできるはずです。

You:

やはりTime Limit Exceedです。 私としては、メモ化再帰によって高速化できると思うのですがいかがでしょうか?

ChatGPT:

おっしゃる通り、メモ化再帰を使って高速化できます。以下のC++コードは、メモ化を実装したものです。dfs関数がキャッシュを引数として受け取り、結果をキャッシュに保存して高速化します。

#include <iostream>
#include <vector>
#include <algorithm>
#include <map>
#include <set>

using namespace std;

vector<vector<int>> tree;
vector<int> values;

void dfs(int v, int parent, map<pair<int, int>, multiset<int, greater<int>>> &cache, int k) {
    if (cache.count({v, k})) return;
    
    multiset<int, greater<int>> &subtree_values = cache[{v, k}];
    subtree_values.insert(values[v - 1]);

    for (int child : tree[v]) {
        if (child != parent) {
            dfs(child, v, cache, k);
            for (int value : cache[{child, k}]) {
                subtree_values.insert(value);
                
                if (subtree_values.size() > k) {
                    subtree_values.erase(prev(subtree_values.end()));
                }
            }
        }
    }
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

    int n, q;
    cin >> n >> q;

    values.resize(n);
    for (int i = 0; i < n; i++) {
        cin >> values[i];
    }

    tree.resize(n + 1);
    for (int i = 0; i < n - 1; i++) {
        int a, b;
        cin >> a >> b;
        tree[a].push_back(b);
        tree[b].push_back(a);
    }

    map<pair<int, int>, multiset<int, greater<int>>> cache;

    for (int i = 0; i < q; i++) {
        int v, k;
        cin >> v >> k;
        dfs(v, 0, cache, k);
        cout << *next(cache[{v, k}].begin(), k - 1) << endl;
    }

    return 0;
}

このコードでは、dfs関数がキャッシュを引数として受け取り、キャッシュに結果を保存して高速化します。main関数内でキャッシュを作成し、クエリごとにdfs関数に渡します。メモ化により、同じ頂点とK_i値に対する部分木の計算が再利用され、計算量が削減されます。これにより、時間制限をクリアできるはずです。

You:

Wrong AnswerとTime Limit Exceedが出ます。うーーん

ChatGPT:

すみません。もう一度考え直しました。以下のC++コードは、部分木に含まれる整数をソート済みのリストに格納し、そのリストをマージする方法で高速化を図っています。この実装では、部分木の整数のマージが効率的に行われるため、計算量が削減され、Wrong AnswerとTime Limit Exceedが解決されるはずです。

#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
#include <limits>

using namespace std;

vector<vector<int>> tree;
vector<int> values;

vector<int> dfs(int v, int parent) {
    vector<int> subtree_values = {values[v - 1]};

    for (int child : tree[v]) {
        if (child != parent) {
            vector<int> child_values = dfs(child, v);
            vector<int> merged_values;
            merged_values.reserve(subtree_values.size() + child_values.size());

            merge(subtree_values.begin(), subtree_values.end(),
                  child_values.begin(), child_values.end(),
                  back_inserter(merged_values), greater<int>());

            subtree_values.swap(merged_values);
        }
    }

    return subtree_values;
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

    int n, q;
    cin >> n >> q;

    values.resize(n);
    for (int i = 0; i < n; i++) {
        cin >> values[i];
    }

    tree.resize(n + 1);
    for (int i = 0; i < n - 1; i++) {
        int a, b;
        cin >> a >> b;
        tree[a].push_back(b);
        tree[b].push_back(a);
    }

    for (int i = 0; i < q; i++) {
        int v, k;
        cin >> v >> k;
        vector<int> subtree_values = dfs(v, 0);

        if (k <= subtree_values.size()) {
            cout << subtree_values[k - 1] << endl;
        } else {
            cout << -1 << endl; // If the subtree doesn't have enough nodes.
        }
    }

    return 0;
}

このコードでは、dfs関数が部分木に含まれる整数をソート済みのリストとして返し、マージ操作を使用して効率的に部分木の整数をマージします。これにより、計算量が削減され、Wrong AnswerとTime Limit Exceedが解決されるはずです。

You:

Time Limit Exceedです。メモ化再帰をやめたのはなぜですか?

ChatGPT:

失礼いたしました。メモ化再帰を再度取り入れた以下のC++コードでは、キャッシュにソート済みのリストを格納し、マージ操作を使用して効率的に部分木の整数をマージします。これにより、計算量が削減され、Time Limit Exceedが解決されるはずです。

#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
#include <limits>
#include <map>

using namespace std;

vector<vector<int>> tree;
vector<int> values;

vector<int> dfs(int v, int parent, map<int, vector<int>> &cache) {
    if (cache.count(v)) return cache[v];

    vector<int> subtree_values = {values[v - 1]};

    for (int child : tree[v]) {
        if (child != parent) {
            vector<int> child_values = dfs(child, v, cache);
            vector<int> merged_values;
            merged_values.reserve(subtree_values.size() + child_values.size());

            merge(subtree_values.begin(), subtree_values.end(),
                  child_values.begin(), child_values.end(),
                  back_inserter(merged_values), greater<int>());

            subtree_values.swap(merged_values);
        }
    }

    return cache[v] = subtree_values;
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

    int n, q;
    cin >> n >> q;

    values.resize(n);
    for (int i = 0; i < n; i++) {
        cin >> values[i];
    }

    tree.resize(n + 1);
    for (int i = 0; i < n - 1; i++) {
        int a, b;
        cin >> a >> b;
        tree[a].push_back(b);
        tree[b].push_back(a);
    }

    map<int, vector<int>> cache;

    for (int i = 0; i < q; i++) {
        int v, k;
        cin >> v >> k;
        vector<int> subtree_values = dfs(v, 0, cache);

        if (k <= subtree_values.size()) {
            cout << subtree_values[k - 1] << endl;
        } else {
            cout << -1 << endl; // If the subtree doesn't have enough nodes.
        }
    }

    return 0;
}

このコードでは、メモ化再帰を使用してキャッシュにソート済みのリストを格納し、マージ操作を使用して効率的に部分木の整数をマージします。これにより、計算量が削減され、Time Limit Exceedが解決されるはずです。

You:

やはりTime Limit ExceedとWrong Answerです。以下の2つを提案します。 1. subtree_valuesは大きい方から高々20個まで保持していれば良いはずです 2. cacheはstd::mapではなく、std::vectorにしたらどうでしょうか?

ChatGPT:

おっしゃる通り、提案された修正を加えることで、計算量の削減とWrong AnswerおよびTime Limit Exceedの解決が期待できます。以下のC++コードは、提案に基づいて修正されたものです。

#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
#include <limits>

using namespace std;

vector<vector<int>> tree;
vector<int> values;

vector<int> dfs(int v, int parent, vector<vector<int>> &cache) {
    if (!cache[v].empty()) return cache[v];

    vector<int> subtree_values = {values[v - 1]};

    for (int child : tree[v]) {
        if (child != parent) {
            vector<int> child_values = dfs(child, v, cache);
            vector<int> merged_values;

            merge(subtree_values.begin(), subtree_values.end(),
                  child_values.begin(), child_values.end(),
                  back_inserter(merged_values), greater<int>());

            subtree_values.swap(merged_values);
            if (subtree_values.size() > 20) {
                subtree_values.resize(20);
            }
        }
    }

    return cache[v] = subtree_values;
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

    int n, q;
    cin >> n >> q;

    values.resize(n);
    for (int i = 0; i < n; i++) {
        cin >> values[i];
    }

    tree.resize(n + 1);
    for (int i = 0; i < n - 1; i++) {
        int a, b;
        cin >> a >> b;
        tree[a].push_back(b);
        tree[b].push_back(a);
    }

    vector<vector<int>> cache(n + 1);

    for (int i = 0; i < q; i++) {
        int v, k;
        cin >> v >> k;
        vector<int> subtree_values = dfs(v, 0, cache);

        if (k <= subtree_values.size()) {
            cout << subtree_values[k - 1] << endl;
        } else {
            cout << -1 << endl; // If the subtree doesn't have enough nodes.
        }
    }

    return 0;
}

このコードでは、以下の修正が行われています。

  1. subtree_valuesを大きい方から高々20個まで保持するようにしました。これにより、計算量が削減されます。
  2. キャッシュをstd::mapからstd::vectorに変更しました。これにより、キャッシュアクセスが高速化されます。

これらの修正により、計算量が削減され、Wrong AnswerおよびTime Limit Exceedが解決されるはずです。

You:

Wrong Answerが出ます。惜しいと思うんですが、修正できますか?

ChatGPT:

すみません、修正に不備がありました。以下に修正を加えたC++コードを示します。

#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
#include <limits>

using namespace std;

vector<vector<int>> tree;
vector<int> values;

vector<int> dfs(int v, int parent, vector<vector<int>> &cache) {
    if (!cache[v].empty()) return cache[v];

    vector<int> subtree_values = {values[v - 1]};

    for (int child : tree[v]) {
        if (child != parent) {
            vector<int> child_values = dfs(child, v, cache);
            vector<int> merged_values;

            merge(subtree_values.begin(), subtree_values.end(),
                  child_values.begin(), child_values.end(),
                  back_inserter(merged_values), greater<int>());

            subtree_values.swap(merged_values);
            if (subtree_values.size() > 20) {
                subtree_values.resize(20);
            }
        }
    }

    cache[v] = subtree_values;
    return subtree_values;
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

    int n, q;
    cin >> n >> q;

    values.resize(n);
    for (int i = 0; i < n; i++) {
        cin >> values[i];
    }

    tree.resize(n + 1);
    for (int i = 0; i < n - 1; i++) {
        int a, b;
        cin >> a >> b;
        tree[a].push_back(b);
        tree[b].push_back(a);
    }

    vector<vector<int>> cache(n + 1);

    for

You:

修正ですが、根が頂点1であることを考慮してください。

ChatGPT:

失礼しました。根が頂点1であることを考慮した修正を加えたC++コードを以下に示します。

#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
#include <limits>

using namespace std;

vector<vector<int>> tree;
vector<int> values;

vector<int> dfs(int v, int parent, vector<vector<int>> &cache) {
    if (!cache[v].empty()) return cache[v];

    vector<int> subtree_values = {values[v - 1]};

    for (int child : tree[v]) {
        if (child != parent) {
            vector<int> child_values = dfs(child, v, cache);
            vector<int> merged_values;

            merge(subtree_values.begin(), subtree_values.end(),
                  child_values.begin(), child_values.end(),
                  back_inserter(merged_values), greater<int>());

            subtree_values.swap(merged_values);
            if (subtree_values.size() > 20) {
                subtree_values.resize(20);
            }
        }
    }

    cache[v] = subtree_values;
    return subtree_values;
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

    int n, q;
    cin >> n >> q;

    values.resize(n);
    for (int i = 0; i < n; i++) {
        cin >> values[i];
    }

    tree.resize(n + 1);
    for (int i = 0; i < n - 1; i++) {
        int a, b;
        cin >> a >> b;
        tree[a].push_back(b);
        tree[b].push_back(a);
    }

    vector<vector<int>> cache(n + 1);

    for (int i = 0; i < q; i++) {
        int v, k;
        cin >> v >> k;
        vector<int> subtree_values = dfs(v, -1, cache);

        if (k <= subtree_values.size()) {
            cout << subtree_values[k - 1] << endl;
        } else {
            cout << -1 << endl; // If the subtree doesn't have enough nodes.
        }
    }

    return 0;
}

このコードでは、dfs関数の呼び出し時に、根が頂点1であることを考慮して、親ノードの初期値を-1に設定しています。これにより、正しい結果が得られるはずです。

You:

クエリを読み始める前に、一度、根を始点と下dfsを実行してください。

ChatGPT:

失礼しました。根を始点としてDFSを事前に実行するように修正しました。以下が修正後のC++コードです。

#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
#include <limits>

using namespace std;

vector<vector<int>> tree;
vector<int> values;

vector<int> dfs(int v, int parent, vector<vector<int>> &cache) {
    if (!cache[v].empty()) return cache[v];

    vector<int> subtree_values = {values[v - 1]};

    for (int child : tree[v]) {
        if (child != parent) {
            vector<int> child_values = dfs(child, v, cache);
            vector<int> merged_values;

            merge(subtree_values.begin(), subtree_values.end(),
                  child_values.begin(), child_values.end(),
                  back_inserter(merged_values), greater<int>());

            subtree_values.swap(merged_values);
            if (subtree_values.size() > 20) {
                subtree_values.resize(20);
            }
        }
    }

    cache[v] = subtree_values;
    return subtree_values;
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

    int n, q;
    cin >> n >> q;

    values.resize(n);
    for (int i = 0; i < n; i++) {
        cin >> values[i];
    }

    tree.resize(n + 1);
    for (int i = 0; i < n - 1; i++) {
        int a, b;
        cin >> a >> b;
        tree[a].push_back(b);
        tree[b].push_back(a);
    }

    vector<vector<int>> cache(n + 1);
    
    // Preprocess by executing DFS with root as the starting point
    dfs(1, -1, cache);

    for (int i = 0; i < q; i++) {
        int v, k;
        cin >> v >> k;
        vector<int> subtree_values = cache[v];

        if (k <= subtree_values.size()) {
            cout << subtree_values[k - 1] << endl;
        } else {
            cout << -1 << endl; // If the subtree doesn't have enough nodes.
        }
    }

    return 0;
}

この修正により、根を始点としてDFSが事前に実行されます。これにより、クエリ時にはキャッシュから効率的に部分木の値を取得できます。

You:

Accepted!

ChatGPT:

素晴らしいです!問題が解決されてうれしいです。今後もプログラミングやアルゴリズムに関する質問があれば、お気軽にお聞きください。お手伝いできることがあれば喜んで対応いたします。

CODINGAME SPRING CHALLENGE 2021 参加記

www.codingame.com

成績

全体 179位,日本人 26位でした. Legend到達は初めてなので嬉しいです.

やったこと

僕の決めたルールに従ってトロロ君に動いてもらいました.ビームサーチを一度書きましたが,あまりに弱くて泣く泣く廃棄しました.

ルール

COMPLETEのルール1:dayの最初にCOMPLETEする

  • COMPLETEすることでsize2の木をGROWしやすくなり,size2の木をGROWすることでsize1の木をGROWしやすくなり,size1の木をGROWすることで(以下略).

COMPLETEのルール2:size2,3の木の数を基準にCOMPLETEする

  • 具体的には #size2 + #size3 >= min(7, 23-day)を満たすときにCOMPLETEする.

COMPLETEのルール3:相手が自分以上にCOMPLETEしているときは,多少無理にでもCOMPLETEする

  • 相手のCOMPLETE数が自分のCOMPLETE数以上の場合は,ルール2を満たさなくても,#size3 >= min(4, 23-day) を満たせばCOMPLETEする.

COMPLETEのルール4:得られるSun Pointが少ない木をCOMPLETEする

  • 現在の盤面のまま3日間経過したときに得られるSun Pointが最も少ないsize3の木をCOMPLETEする.

SEEDのルール1:種は1つまで

  • 2個以上の種を同時に盤面に置かない

SEEDのルール2:自分の木の陰にならない場所にSEEDする

  • 6方向について3つ先まで隣接セルを見て,そこに存在する自分の木の数をカウントし,これをAとする

  • 種が届く場所で,なおかつ,-4A+richness が最も大きいセルにSEEDする

GROWのルール1: size1の木は1本,size2の木は3本まで

  • この制限を付けることで,勝手にGROW2->3,GROW1->2,GROW0->1,SEEDの順で動いてくれるようになる

GROWのルール2:終盤はGROWを絞る

  • 21日目以降は,size2かつrichness3の木だけを育成する.

感想

こどげは本当に楽しいですね. 次はお気持ちルールベースではなく,ちゃんとした探索アルゴリズムで勝ちに行きたいです. あと,TERRYさんオススメの本買ったので,積読しないでちゃんと読みたいですね.

f:id:tabae326:20210518200722j:plain

CODINGAME FALL CHALLENGE 2020に参加しました

CODINGAME FALL CHALLENGE 2020

www.codingame.com

終結

f:id:tabae326:20201123212757p:plain
Final result

やったこと

この手のコンテストはほとんどの初めてで,試行錯誤しながらいろいろやるのが楽しかったです(前回のパックマンは最終日しか参加してない).

初期LEARN

下から6枚とるだけです.いろいろ試しましたが,結局これに落ち着きました.

全探索

4ターン先まで全探索しました.

  • 相手の動きは考慮していません.
  • CAST([2,0,0,0]以外の初期呪文を除く), BREW, LEARN(最初のターンのみ),RESTを探索しました.
  • 評価値は(price + 1) * board
    • price := 4ターン先までの取得price
    • board := # of tier_i * (i+1)
  • DFSで書きました.

やってみたかったこと

  • ビームサーチとかやってみたかったんですが,DFS->BFSに書き換えるのが大変そうで見送ってしまいました.
  • 高速化したかったですね.vectorを参照渡ししてDFSしてたのですが,なんかもっと頭の良い方法がありそうです.

感想

改善して強くなってくれるとうれしかったです. 修論がやばいです.

どうでも良いこと

咲っていう麻雀マンガが好きなんですが,咲-阿知賀編-の主人公が数巡先を読む能力を持つ園城寺怜という子なんですね. 数巡先を読むという点で今回の全探索と似てるなあと思って,全探索関数をfuturisticePlayer命名してにこにこしてました(きもい)(futuristic playerは咲-阿知賀編-のED).

【Upsolve】 Remove the Substrings (hard version) - Codeforces Round #579 (Div. 3) D2

問題

codeforces.com

解法

  1. sの右側から貪欲にtの要素を拾っていったときに,t[i:|t|]が作れる最初のインデックスxをr[i]と定義します.言い換えると,s[x:|s|]からt[i:|t|]を作ることができるような最も右側のxをr[i]と定義します.各i(1<= i <= |t|)についてr[i]を事前に計算しておきます.

  2. 各j(1<= j <= |s|)について,jから最大で何文字切り取れるか,を全探索します.つまり,s[j:y)を切り取ったあとの文字列からtを作ることができるような最も右側のyを探します.ここで,左側から貪欲にtの要素を拾っていったときに,0~j-1の間でt[next-1]文字まで回収できていたとします.このとき,次に必要となる要素はt[next]です.r[i]の定義からyを最大化するためには,y=r[next]とすれば良いことが分かります.よって,jから最大で何文字切り取れるかの答えはr[next]-jとなります.ただし,そもそもtを作れないケースや,jまででtの要素をすべて回収できるケースがあるので,別途場合分けが必要です.

ソースコード

codeforces.com

躓いたところ

何を固定して全探索すれば良いか分からなかった. 間に合うはずのないDFSを書いてしまったのも反省点.

AtCoderで水色になりました

今週末のAGCで緑に戻る可能性があるので,今のうちに書いておこうと思います.

f:id:tabae326:20200522145756p:plain
精進グラフ

自己紹介

こんにちは,bbgeです.これはかなり適当につけた名前で,最近改名したい気分になっています.AtCoderのIDはtabae326です.IDとなんの関係もないのも気持ち悪いですね.

さて,自己紹介ですが,仙台にある大学で修士2年生をしています.機械系の学科に所属していて,いわゆる四力(熱力学,材料力学,流体力学,機械力学)や,制御工学,製図,etc...などを勉強していました(もう忘れた).研究室はHPC(High Performance Computing)の研究室で,スーパーコンピュータで遊んでいます.なぜこの研究室が機械系に所属しているのかは謎です.

AtCoderをちゃんと始めたのは今年の1月からです.AtCoderの存在は結構前から知っていて,AtCoderのコンテストに初めて参加したのは2018年の6月だったのですが,一度参加したきりでその後ずっと放置していました.当時はほとんど毎週末サークルかバイトがあったからだと思います.もっと早く始めたかったな~という気持ちはありますが,考えても仕方ないので心に蓋をしています.

水色までにやったこと

  • ABC C問題をだいたい埋めました
  • 令和ABC D問題を埋めました
  • Boot Camp for Beginners のhardを埋めました
  • 毎日あさかつ/よるかつのどちらかには参加するようにしました

AtCoder Problems開発者の宇宙ツイッタラーXさん,あさかつ主催してくださるタキガワさん,よるかつ主催してくださるmikhailさん,いつもありがとうございます.大大大大感謝です.

今使えるアルゴリズム

これに関しては,同レート帯の他の人に比べて少ない気がしています.アルゴリズムを網羅的に勉強したことはなくて,問題を解いていて遭遇したら適宜勉強する感じでやっています.

理解したうえで使える(たぶん習得順)

  • bit全探索
  • 累積和
  • BFS
  • 二分探索
  • DFS
  • ダイクストラ
  • ナップサックDP
  • 桁DP

あまり理解してないけど使っている

使えるようになりたい

  • Fenwick Tree(BIT)
  • 座標圧縮
  • Segment Tree
  • bitDP
  • 二次元累積和
  • 三分探索

スニペットが整備されているもの

今後の目標

AtCoderを始めた当初の目標は水色だったので一応達成できました.ですが,今は青色を目指したい気持ちです.とても長い道のりになりそうな気がしています.頑張ります.