Problem: cf1089_c
Difficulty: medium
Categories: graph, greedy, search
Time limit: 3000 ms, memory limit: 512 MB

If you want to make an array problem harder - solve it on a tree. If you want to make a tree problem harder - solve it on a cactus
Conventional wisdom
NEERC featured a number of problems in previous years about cactuses — connected undirected graphs in which every edge belongs to at most one simple cycle. Intuitively, a cactus is a generalization of a tree where some cycles are allowed. An example of a cactus from NEERC 2007 problem is given on the picture below.
![image](https://cdn-mineru.openxlab.org.cn/result/2026-01-05/48855c1b-a5da-4c0e-b1d5-0e0d7f736bb8/3e5597fdf3edfe843267780e831b3d4e3dea1c15226b146616094d16bb471ecd.jpg)

You are playing a game on a cactus with Chloe. You are given a cactus. Chloe had secretly picked one vertex  $v$  from the cactus and your goal is to find it. You can make at most 10 guesses. If your guess is vertex  $v$  — you win. Otherwise, if your guess is another vertex  $u$  — Chloe helps you and tells you some vertex  $w$  which is adjacent to  $u$  and such that the distance from  $w$  to  $v$  is strictly less than the distance from  $u$  to  $v$  (here the distance is the number of edges in the shortest path between vertices).
# Interaction Protocol
First, the testing system writes a line with two integers  $n$  and  $m$  ( $1 \leq n \leq 500; 0 \leq m \leq 500$ ). Here  $n$  is the number of vertices in the graph. Vertices are numbered from 1 to  $n$ . Edges of the graph are represented by a set of edge-distinct paths, where  $m$  is the number of such paths. Each of the following  $m$  lines contains a path in the graph. A path starts with an integer  $k_i$  ( $2 \leq k_i \leq 500$ ) followed by  $k_i$  integers from 1 to  $n$ . These  $k_i$  integers represent vertices of a path. Adjacent vertices in a path are distinct. The path can go to the same vertex multiple times, but every edge is traversed exactly once in the whole input. The graph in the input is a cactus.
To prove that your program can find a secret vertex in at most 10 queries, you need to do that  $n$  times. Each time testing system picks some vertex before interacting with your program. Your program makes guesses by writing lines with a single number  $u$  ( $1 \leq u \leq n$ ).
Testing system responds by writing lines with one of the two responses:
- "FOUND" — means that your guess is correct. After that, your program should proceed to the next test case or terminate if  $n$  vertices were already guessed.
- "GO  $w$ " — means that your guess is incorrect, but now you know that the distance from vertex  $w$  to the secret vertex is less than the distance from  $u$ . It is guaranteed that vertices  $u$  and  $w$  are connected by an edge.
Do not forget to flush the output after each guess!
Page 1 of 2

Example

<table><tr><td>standard input</td><td>standard output</td><td>Illustration</td></tr><tr><td>5 2</td><td></td><td></td></tr><tr><td>5 1 2 3 4 5</td><td></td><td></td></tr><tr><td>2 1 3</td><td></td><td></td></tr><tr><td>FOUND</td><td>3</td><td></td></tr><tr><td>GO 4</td><td>3</td><td></td></tr><tr><td>FOUND</td><td>4</td><td></td></tr><tr><td>GO 2</td><td>3</td><td></td></tr><tr><td>FOUND</td><td>2</td><td></td></tr><tr><td>GO 1</td><td>3</td><td></td></tr><tr><td>FOUND</td><td>1</td><td></td></tr><tr><td>GO 4</td><td>3</td><td></td></tr><tr><td>GO 5</td><td>4</td><td></td></tr><tr><td>FOUND</td><td>5</td><td></td></tr></table>
# Note
Empty lines are added to the standard input and the standard output examples for clarity only. They are not present during the actual interaction.
Page 2 of 2

Non-adaptive interactor (C++):
#include "testlib.h"
#include <bits/stdc++.h>
using namespace std;

static long long queries = 0;
static long long query_limit = 0;

static void log_metrics() {
    try {
        tout << "queries=" << queries << "\n";
        tout << "query_limit=" << query_limit << "\n";
        tout.flush();
    } catch (...) {
        
    }
}

static void finish(TResult verdict, const string& msg) {
    log_metrics();
    quitf(verdict, "%s", msg.c_str());
}

static bool parsePositiveInt(const string& s, long long& out) {
    if (s.empty()) return false;
    long long val = 0;
    size_t i = 0;
    if (s[0] == '+') {
        if (s.size() == 1) return false;
        i = 1;
    }
    for (; i < s.size(); ++i) {
        unsigned char c = (unsigned char)s[i];
        if (c < '0' || c > '9') return false;
        int d = (int)(c - '0');
        if (val > (LLONG_MAX - d) / 10) return false;
        val = val * 10 + d;
    }
    out = val;
    return true;
}

int main(int argc, char** argv) {
    registerInteraction(argc, argv);
    atexit(log_metrics);

    int n = 0, m = 0;
    n = inf.readInt(1, 500, "n");
    m = inf.readInt(0, 500, "m");

    vector<vector<int>> paths;
    paths.reserve(m);

    vector<vector<int>> adj(n + 1);
    unordered_set<long long> seen;
    seen.reserve(2048);

    auto addEdge = [&](int u, int v) {
        if (u == v) return;
        int a = min(u, v), b = max(u, v);
        long long key = (long long)a * 1000LL + (long long)b;
        if (seen.insert(key).second) {
            adj[u].push_back(v);
            adj[v].push_back(u);
        }
    };

    for (int i = 0; i < m; ++i) {
        int k = inf.readInt(2, 500, ("k_" + to_string(i + 1)).c_str());
        vector<int> p(k);
        for (int j = 0; j < k; ++j) {
            p[j] = inf.readInt(1, n, ("v_" + to_string(i + 1) + "_" + to_string(j + 1)).c_str());
            if (j && p[j] == p[j - 1]) {
                
                finish(_fail, "Invalid input: adjacent equal vertices in a path");
            }
        }
        for (int j = 0; j + 1 < k; ++j) addEdge(p[j], p[j + 1]);
        paths.push_back(std::move(p));
    }

    for (int u = 1; u <= n; ++u) {
        sort(adj[u].begin(), adj[u].end());
        adj[u].erase(unique(adj[u].begin(), adj[u].end()), adj[u].end());
    }

    
    vector<int> secrets;
    secrets.reserve(n);
    if (!inf.seekEof()) {
        for (int i = 0; i < n; ++i) {
            if (inf.seekEof()) break;
            secrets.push_back(inf.readInt(1, n, ("secret_" + to_string(i + 1)).c_str()));
        }
    }
    if ((int)secrets.size() != n) {
        secrets.clear();
        for (int i = 1; i <= n; ++i) secrets.push_back(i);
    }

    
    const int INF = 1e9;
    vector<vector<int>> dist(n + 1, vector<int>(n + 1, INF));
    for (int s = 1; s <= n; ++s) {
        deque<int> q;
        dist[s][s] = 0;
        q.push_back(s);
        while (!q.empty()) {
            int u = q.front();
            q.pop_front();
            int du = dist[s][u];
            for (int v : adj[u]) {
                if (dist[s][v] > du + 1) {
                    dist[s][v] = du + 1;
                    q.push_back(v);
                }
            }
        }
    }

    query_limit = 10LL * n;
    const long long hard_cap = max(200000LL, 10LL * query_limit);

    
    cout << n << " " << m << endl;
    for (int i = 0; i < m; ++i) {
        cout << (int)paths[i].size();
        for (int v : paths[i]) cout << " " << v;
        cout << endl;
    }
    cout.flush();

    
    log_metrics();

    for (int round = 0; round < n; ++round) {
        int secret = secrets[round];

        while (true) {
            string tok;
            if (!(cin >> tok)) {
                finish(_pe, "Unexpected EOF from contestant");
            }

            long long u_ll = 0;
            if (!parsePositiveInt(tok, u_ll)) {
                finish(_pe, "Malformed token (expected integer vertex id)");
            }
            if (u_ll < 1 || u_ll > n) {
                finish(_pe, "Vertex id out of range");
            }
            int u = (int)u_ll;

            ++queries;
            if (queries > hard_cap) {
                finish(_pe, "Hard cap exceeded (possible infinite loop / spam)");
            }

            if (u == secret) {
                cout << "FOUND" << endl;
                cout.flush();
                break;
            }

            if (dist[u][secret] >= INF) {
                finish(_wa, "Graph appears disconnected (no path to secret)");
            }

            int bestW = -1;
            int du = dist[u][secret];
            for (int w : adj[u]) {
                if (dist[w][secret] < du) {
                    if (bestW == -1 || w < bestW) bestW = w;
                }
            }

            if (bestW == -1) {
                finish(_wa, "No valid GO move exists for this query");
            }

            cout << "GO " << bestW << endl;
            cout.flush();
        }
    }

    finish(_ok, "OK");
    return 0;
}


Adaptive interactor (C++):
#include "testlib.h"
#include <bits/stdc++.h>
using namespace std;

static long long queries = 0;
static long long query_limit = 0;

static void log_metrics() {
    try {
        tout << "queries=" << queries << "\n";
        tout << "query_limit=" << query_limit << "\n";
        tout.flush();
    } catch (...) {
        
    }
}

static void finish(TResult verdict, const string& msg) {
    log_metrics();
    quitf(verdict, "%s", msg.c_str());
}

static bool isAllDigits(const string& s) {
    if (s.empty()) return false;
    for (char c : s) if (c < '0' || c > '9') return false;
    return true;
}

int main(int argc, char** argv) {
    registerInteraction(argc, argv);
    atexit(log_metrics);

    int n = 0, m = 0;
    n = inf.readInt(1, 500, "n");
    m = inf.readInt(0, 500, "m");

    query_limit = 10LL * n;
    log_metrics(); 

    vector<vector<int>> paths;
    paths.reserve(m);

    set<pair<int,int>> edges;
    vector<vector<int>> adj(n + 1);

    auto addEdge = [&](int u, int v) {
        if (u == v) finish(_fail, "Invalid test: self-loop");
        int a = min(u, v), b = max(u, v);
        if (edges.insert({a, b}).second) {
            adj[u].push_back(v);
            adj[v].push_back(u);
        }
    };

    for (int i = 0; i < m; ++i) {
        int k = inf.readInt(2, 500, "k");
        vector<int> p(k);
        for (int j = 0; j < k; ++j) p[j] = inf.readInt(1, n, "v");
        for (int j = 0; j + 1 < k; ++j) addEdge(p[j], p[j + 1]);
        paths.push_back(std::move(p));
    }

    for (int i = 1; i <= n; ++i) {
        sort(adj[i].begin(), adj[i].end());
        adj[i].erase(unique(adj[i].begin(), adj[i].end()), adj[i].end());
    }

    
    cout << n << " " << m << "\n";
    for (const auto& p : paths) {
        cout << (int)p.size();
        for (int v : p) cout << " " << v;
        cout << "\n";
    }
    cout.flush();

    
    const int INF = 1e9;
    vector<vector<int>> dist(n + 1, vector<int>(n + 1, INF));
    for (int s = 1; s <= n; ++s) {
        deque<int> q;
        dist[s][s] = 0;
        q.push_back(s);
        while (!q.empty()) {
            int u = q.front();
            q.pop_front();
            for (int v : adj[u]) {
                if (dist[s][v] == INF) {
                    dist[s][v] = dist[s][u] + 1;
                    q.push_back(v);
                }
            }
        }
    }

    long long hard_cap = max(200000LL, 10LL * query_limit);

    vector<int> candidates;
    vector<char> inSet(n + 1, 0);

    auto resetRound = [&]() {
        candidates.clear();
        candidates.reserve(n);
        fill(inSet.begin(), inSet.end(), 0);
        for (int v = 1; v <= n; ++v) {
            candidates.push_back(v);
            inSet[v] = 1;
        }
    };

    for (int round = 1; round <= n; ++round) {
        resetRound();
        while (true) {
            string tok;
            if (!(cin >> tok)) finish(_pe, "Unexpected EOF from contestant");
            if (!isAllDigits(tok)) finish(_pe, "Malformed query token (expected integer)");

            long long uu = 0;
            for (char c : tok) {
                uu = uu * 10 + (c - '0');
                if (uu > (long long)n + 10LL) break;
            }
            if (uu < 1 || uu > n) finish(_pe, "Guessed vertex out of range");
            int u = (int)uu;

            ++queries;
            if (queries > hard_cap) finish(_pe, "Too many queries (hard cap exceeded)");

            if ((int)candidates.size() == 1 && candidates[0] == u) {
                cout << "FOUND\n";
                cout.flush();
                break;
            }

            if (adj[u].empty()) {
                
                finish(_fail, "Invalid test: isolated vertex encountered in interaction");
            }

            int bestW = -1;
            int bestCnt = -1;

            for (int w : adj[u]) {
                int cnt = 0;
                for (int v : candidates) {
                    if (dist[w][v] < dist[u][v]) ++cnt;
                }
                if (cnt > bestCnt || (cnt == bestCnt && w < bestW)) {
                    bestCnt = cnt;
                    bestW = w;
                }
            }

            if (bestCnt <= 0 || bestW == -1) {
                finish(_fail, "Internal error: no valid GO reply keeps feasibility");
            }

            vector<int> nextCandidates;
            nextCandidates.reserve(bestCnt);
            fill(inSet.begin(), inSet.end(), 0);
            for (int v : candidates) {
                if (dist[bestW][v] < dist[u][v]) {
                    nextCandidates.push_back(v);
                    inSet[v] = 1;
                }
            }
            candidates.swap(nextCandidates);

            if (candidates.empty()) finish(_fail, "Internal error: feasibility became empty");

            cout << "GO " << bestW << "\n";
            cout.flush();
        }
    }

    finish(_ok, "OK");
}


Test generator (C++):
#include "testlib.h"
#include <bits/stdc++.h>
using namespace std;

static void addEdge(vector<pair<int,int>>& edges, int u, int v) {
    ensuref(u != v, "Self-loop is not allowed");
    if (u > v) swap(u, v);
    edges.push_back({u, v});
}

static vector<int> makePermutation(int n) {
    vector<int> p(n);
    iota(p.begin(), p.end(), 1);
    for (int i = n - 1; i >= 1; --i) {
        int j = rnd.next(0, i);
        swap(p[i], p[j]);
    }
    return p;
}

int main(int argc, char** argv) {
    registerGen(argc, argv, 1);

    string mode = opt<string>("mode", "non");
    int nOpt = opt<int>("n", 500);

    int seed = 1;
    if (argc >= 2) {
        
        seed = atoi(argv[1]);
        if (seed <= 0) seed = 1;
    }

    int n = max(1, min(500, nOpt));
    vector<pair<int,int>> edges;

    if (seed == 1) {
        
        n = 1;
    } else if (seed == 2) {
        
        n = 500;
        for (int i = 1; i < n; ++i) addEdge(edges, i, i + 1);
        addEdge(edges, n, 1);
    } else if (seed == 3) {
        
        n = 500;
        for (int i = 1; i < n; ++i) addEdge(edges, i, i + 1);
    } else {
        
        
        if (n == 1) {
            
        } else if (n == 2) {
            addEdge(edges, 1, 2);
        } else {
            int maxSpare = min(200, n - 3);
            int spare = rnd.next(0, maxSpare);
            int baseLen = n - spare;
            if (baseLen < 3) baseLen = 3;
            if (baseLen > n) baseLen = n;

            
            for (int i = 1; i < baseLen; ++i) addEdge(edges, i, i + 1);
            addEdge(edges, baseLen, 1);

            int cur = baseLen + 1;
            int extra = n - baseLen;

            
            while (extra > 0) {
                int attach = rnd.next(1, baseLen);

                int maxLen = min(60, extra);
                int minLen = 1;
                if (rnd.next(0, 99) < 55) {
                    
                    minLen = max(1, min(maxLen, extra / 4));
                }
                int len = (minLen == maxLen) ? minLen : rnd.next(minLen, maxLen);

                int prev = attach;
                for (int i = 0; i < len; ++i) {
                    int v = cur++;
                    addEdge(edges, prev, v);
                    prev = v;
                }
                extra -= len;
            }
        }
    }

    ensuref(n >= 1 && n <= 500, "n out of range");
    ensuref((int)edges.size() <= 500, "Too many edges for m<=500");

    int m = (int)edges.size();
    cout << n << " " << m << "\n";
    for (auto [u, v] : edges) {
        cout << 2 << " " << u << " " << v << "\n";
    }

    if (mode == "non") {
        
        
        vector<int> perm = makePermutation(n);
        for (int i = 0; i < n; ++i) {
            if (i) cout << ' ';
            cout << perm[i];
        }
        cout << "\n";
    } else if (mode == "adp") {
        
    } else {
        quitf(_fail, "Unknown --mode: %s", mode.c_str());
    }

    return 0;
}