Problem: icpc2025_asia_east_continent_online_ii_i
Difficulty: medium
Categories: math
Time limit: 3000 ms, memory limit: 1024 MB

This is an interactive problem.

Given a directed acyclic graph (DAG) with $n$ vertices and $m$ edges, each edge has an associated weight.

There may be multiple paths between any pair of vertices $s$ and $t$ . We define the weight of a path as the product of the weights of all edges along that path. Let $f ( s , t , c )$ denote the sum of the path weights of all distinct paths from $s$ to $t$ after multiplying the weight of every edge in the graph by $c$ . Since the result can be extremely large, $f ( s , t , c )$ is taken modulo 998244353.

Xiao M will inform you of the structure of the DAG in advance (excluding the edge weights).

You may provide parameters $s$ , $t$ , and $c$ ; the interactor will return the value of $f ( s , t , c )$ . You are allowed to make at most 999 such queries to obtain certain information about the graph. After several query cycles, the interactor will provide a parameter $k$ , and you need to determine the value of $f ( 1 , n , k )$ .

# Input

The first line contains two positive integers $n$ and $m$ $1 \leq n \leq 1 0 0 0 , 1 \leq m \leq 5 0 0 0$ ), where $n$ represents the number of vertices in the graph and $m$ represents the number of edges.

In the following $m$ lines, each line contains two positive integers $x$ and $y$ $1 \leq x , y \leq 1 0 0 0 \rangle$ , indicating that there is a directed edge from vertex $x$ to vertex $y$ in the graph. It is guaranteed that the graph is a Directed Acyclic Graph (DAG).

# Interaction Protocol

For each query, you need to output in the format $\texttt { ? s t c } ( 1 \leq s , t \leq n , 0 \leq c < 9 9 8 2 4 4 3 5 3 )$ where $s , t , c$ are all integers, and the interactor will return the answer of $f ( s , t , c )$ .

When you are confident that the information you have is sufficient to answer the query, output !, and the interactor will return a integer parameter $k$ ( $0 \leq k < 9 9 8 2 4 4 3 5 3 $ , which means you are asked for the weight of $f ( 1 , n , k )$ .

You need to output the result of $f ( 1 , n , k )$ .

After printing a query or the answer, do not forget to output the end of the line and flush the output. Otherwise, you will get the verdict Idleness Limit Exceeded. To do this, use:

fflush(stdout) or cout.flush() in $\mathrm { C } + +$ ; System.out.flush() in Java; flush(output) in Pascal;   
stdout.flush() in Python; see the documentation for other languages.

# Example

<table><tr><td>standard input</td><td>standard output</td></tr><tr><td>33</td><td>？121</td></tr><tr><td>12</td><td></td></tr><tr><td>23</td><td>？231</td></tr><tr><td>13</td><td></td></tr><tr><td>1</td><td>？131</td></tr><tr><td>2</td><td>！</td></tr><tr><td>12</td><td>31488</td></tr><tr><td>123</td><td></td></tr></table>

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


const int MOD = 998244353;
const long long QUERY_LIMIT = 999;
const long long HARD_CAP = 20000; 


int N, M;
struct Edge {
    int v;
    int w;
};
vector<vector<Edge>> adj;
int K_param;
long long queries = 0;



vector<int> memo;



void log_metrics() {
    try {
        tout << "queries=" << queries << endl;
        tout << "query_limit=" << QUERY_LIMIT << endl;
    } catch (...) {
        
    }
}


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



int solve(int u, int target, int c) {
    if (u == target) return 1; 
    if (memo[u] != -1) return memo[u];

    long long res = 0;
    for (const auto& e : adj[u]) {
        
        long long ew = (1LL * e.w * c) % MOD;
        if (ew == 0) continue; 

        int val = solve(e.v, target, c);
        res = (res + ew * val) % MOD;
    }
    return memo[u] = res;
}


int get_f(int s, int t, int c) {
    if (s == t) return 1;
    
    fill(memo.begin(), memo.end(), -1);
    return solve(s, t, c);
}

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

    
    N = inf.readInt();
    M = inf.readInt();

    adj.resize(N + 1);
    memo.resize(N + 1);

    vector<pair<int, int>> structure;
    structure.reserve(M);

    for (int i = 0; i < M; ++i) {
        int u = inf.readInt();
        int v = inf.readInt();
        int w = inf.readInt();
        adj[u].push_back({v, w});
        structure.push_back({u, v});
    }
    K_param = inf.readInt();

    
    cout << N << " " << M << endl;
    for (const auto& p : structure) {
        cout << p.first << " " << p.second << endl;
    }
    cout.flush();

    
    log_metrics(); 

    while (true) {
        
        if (queries > HARD_CAP) {
            finish(_pe, "Hard query limit exceeded (infinite loop protection)");
        }

        string token;
        
        if (!(cin >> token)) {
            finish(_wa, "Unexpected EOF (expected '?' or '!')");
        }

        if (token == "!") {
            
            cout << K_param << endl; 
            
            int user_ans;
            if (!(cin >> user_ans)) {
                finish(_wa, "Failed to read answer after '!'");
            }

            int correct_ans = get_f(1, N, K_param);
            if (user_ans == correct_ans) {
                finish(_ok, "Correct answer");
            } else {
                finish(_wa, format("Wrong answer: expected %d, got %d", correct_ans, user_ans));
            }
        } else {
            
            int s, t, c;
            
            
            if (token == "?") {
                if (!(cin >> s >> t >> c)) {
                    finish(_wa, "Invalid query format");
                }
            } else {
                
                try {
                    s = stoi(token);
                } catch (...) {
                    finish(_wa, "Invalid token (expected integer, '?', or '!')");
                }
                if (!(cin >> t >> c)) {
                    finish(_wa, "Invalid query format (expected t c)");
                }
            }

            
            if (s < 1 || s > N || t < 1 || t > N) {
                finish(_wa, format("Query vertices out of range: %d %d", s, t));
            }
            if (c < 0 || c >= MOD) {
                finish(_wa, format("Query constant out of range: %d", c));
            }

            queries++;
            
            if (queries % 10 == 0) log_metrics();

            
            int ans = get_f(s, t, c);
            cout << ans << endl;
        }
    }

    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 = 999; 

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

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

const int MOD = 998244353;

struct Edge {
    int to;
    int w;
};

int n, m;
vector<vector<Edge>> adj;
vector<int> topo_order;
int K_param;

void compute_topo() {
    vector<int> in_degree(n + 1, 0);
    for (int u = 1; u <= n; u++) {
        for (const auto& e : adj[u]) {
            in_degree[e.to]++;
        }
    }
    queue<int> q;
    for (int i = 1; i <= n; i++) {
        if (in_degree[i] == 0) q.push(i);
    }
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        topo_order.push_back(u);
        for (const auto& e : adj[u]) {
            in_degree[e.to]--;
            if (in_degree[e.to] == 0) q.push(e.to);
        }
    }
}



int solve(int s, int t, int c) {
    if (s == t) return 1;
    
    
    static vector<int> dp;
    if ((int)dp.size() <= n) dp.resize(n + 1);
    fill(dp.begin(), dp.end(), 0);
    
    dp[s] = 1;
    
    
    for (int u : topo_order) {
        if (dp[u] == 0) continue;
        
        long long current_val = dp[u];
        for (const auto& e : adj[u]) {
            long long edge_contribution = (1LL * e.w * c) % MOD;
            long long add = (current_val * edge_contribution) % MOD;
            dp[e.to] = (dp[e.to] + add) % MOD;
        }
    }
    
    return dp[t];
}

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

    
    n = inf.readInt(1, 1000, "n");
    m = inf.readInt(1, 5000, "m");
    inf.readEoln();

    adj.resize(n + 1);
    vector<pair<int, int>> edges;
    for (int i = 0; i < m; i++) {
        int u = inf.readInt(1, n, "u");
        inf.readSpace();
        int v = inf.readInt(1, n, "v");
        inf.readSpace();
        int w = inf.readInt(0, MOD - 1, "w");
        inf.readEoln();
        adj[u].push_back({v, w});
        edges.push_back({u, v});
    }
    K_param = inf.readInt(0, MOD - 1, "K");

    
    compute_topo();

    
    cout << n << " " << m << endl;
    for (const auto& p : edges) {
        cout << p.first << " " << p.second << endl;
    }
    cout.flush();

    log_metrics();

    int hard_cap = 15000; 

    while (true) {
        if (queries > hard_cap) {
            finish(_pe, "Hard query limit exceeded");
        }

        string type;
        if (!(cin >> type)) {
            finish(_pe, "Unexpected end of input from contestant");
        }

        if (type == "?") {
            queries++;
            if (queries % 50 == 0) log_metrics();

            int s, t, c;
            if (!(cin >> s >> t >> c)) finish(_pe, "Invalid query format");
            
            if (s < 1 || s > n || t < 1 || t > n || c < 0 || c >= MOD) {
                finish(_pe, "Query parameters out of range");
            }

            int ans = solve(s, t, c);
            cout << ans << endl; 
        } 
        else if (type == "!") {
            cout << K_param << endl;
            
            int ans;
            if (!(cin >> ans)) finish(_pe, "Expected answer after !");
            
            int expected = solve(1, n, K_param);
            if (ans == expected) finish(_ok, "Correct");
            else finish(_wa, format("Expected %d, found %d", expected, ans));
        } 
        else {
            finish(_pe, format("Unknown query type '%s'", type.c_str()));
        }
    }

    return 0;
}


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

const int MOD = 998244353;

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

    
    string mode = opt<string>("mode", "non");
    int n = opt<int>("n", -1);
    int m = opt<int>("m", -1);

    
    if (n == -1) {
        
        int t = rnd.next(100);
        if (t < 5) n = rnd.next(2, 5);           
        else if (t < 15) n = rnd.next(6, 20);    
        else if (t < 40) n = rnd.next(21, 100);  
        else n = rnd.next(900, 1000);            
    }
    
    n = max(n, 2);

    long long max_edges = 1LL * n * (n - 1) / 2;
    int limit_m = (int)min((long long)5000, max_edges);

    
    if (m == -1) {
        int t = rnd.next(100);
        if (t < 20) m = rnd.next(1, (int)min((long long)n * 2, (long long)limit_m)); 
        else if (t < 50) m = rnd.next(1, limit_m); 
        else m = rnd.next(max(1, limit_m - (int)min((long long)2000, max_edges/2)), limit_m); 
    }
    m = max(1, min(m, limit_m));

    
    
    vector<int> p(n);
    iota(p.begin(), p.end(), 1);
    shuffle(p.begin(), p.end());

    
    
    if (rnd.next(10) > 0) { 
        int pos1 = -1, posn = -1;
        for(int i=0; i<n; ++i) {
            if (p[i] == 1) pos1 = i;
            if (p[i] == n) posn = i;
        }
        if (pos1 > posn) swap(p[pos1], p[posn]);
    }

    set<pair<int,int>> edges_set;

    
    int pos1 = -1, posn = -1;
    for(int i=0; i<n; ++i) {
        if (p[i] == 1) pos1 = i;
        if (p[i] == n) posn = i;
    }

    if (pos1 != -1 && posn != -1 && pos1 < posn) {
        int curr = pos1;
        
        while(curr != posn && (int)edges_set.size() < m) {
            int next_node = posn;
            
            if (posn - curr > 1) {
                
                int jump_range = min(posn - curr, 10);
                if (rnd.next(5) == 0) jump_range = posn - curr; 
                int jump = rnd.next(1, jump_range);
                next_node = curr + jump;
            }
            edges_set.insert({p[curr], p[next_node]});
            curr = next_node;
        }
    }

    
    if (m > max_edges / 2) {
        
        vector<pair<int,int>> cands;
        cands.reserve(max_edges);
        for(int i=0; i<n; ++i) {
            for(int j=i+1; j<n; ++j) {
                if (!edges_set.count({p[i], p[j]})) {
                    cands.push_back({p[i], p[j]});
                }
            }
        }
        shuffle(cands.begin(), cands.end());
        for(auto &e : cands) {
            if ((int)edges_set.size() == m) break;
            edges_set.insert(e);
        }
    } else {
        
        while((int)edges_set.size() < m) {
            int u = rnd.next(0, n - 2);
            int v = rnd.next(u + 1, n - 1);
            edges_set.insert({p[u], p[v]});
        }
    }

    
    vector<tuple<int,int,int>> final_edges;
    final_edges.reserve(m);

    for(auto &e : edges_set) {
        int w;
        if (mode == "adp") {
            
            int type = rnd.next(100);
            if (type < 15) w = 0;
            else if (type < 30) w = 1;
            else if (type < 35) w = MOD - 1;
            else w = rnd.next(MOD);
        } else {
            
            w = rnd.next(MOD);
        }
        final_edges.emplace_back(e.first, e.second, w);
    }

    
    shuffle(final_edges.begin(), final_edges.end());

    
    int k = rnd.next(MOD);

    
    cout << n << " " << m << "\n";
    for(auto &[u, v, w] : final_edges) {
        cout << u << " " << v << " " << w << "\n";
    }
    cout << k << "\n";

    return 0;
}