Problem: icpc2022_bapc_d
Difficulty: easy
Categories: greedy, search
Time limit: 2000 ms, memory limit: 256 MB

At the Bacteria And Protein Centre, you own a large collection of DNA. In fact, new strands of DNA come in all the time. To organise the vast amount of data, you identify each piece by its unique substrings: substrings that do not already occur in the database.

Your database can quickly determine whether a given piece of DNA occurs as a substring in the database or not. Naturally, if a certain DNA string is found in the database, it also contains all its substrings.

![](images/42ca1e80023c37d2cb76250f86ff30b6c4a748396bbe9a006665e860d0632c9f.jpg)  
CC BY-NC-SA 2.0 by Argonne National Laboratory on Flickr

You now want to determine the uniqueness of a given piece of DNA: the maximal number of disjoint substrings it contains that are absent from the database.

You are given the length $n$ of the query string $q _ { 1 } \ldots q _ { n }$ , and you can repeatedly ask the database whether it contains the substring $q _ { i } \ldots q _ { j - 1 }$ .

As an example, consider the first sample interaction. In this case, the database contains strings “TGC” and “CT”, and the query string is “CTGCAA”. It has uniqueness 3, because it can be split into the new substrings “CTGC”, “A”, and “A”. The new substring “CTGC” cannot be split up further: for example, the subdivision “CT” and “ $\mathrm { G C }$ ” is not allowed, because both substrings occur (possibly as substrings) in the database. Note that the actual characters in the string are not used in the interaction.

You may use at most $2 n$ queries to the database.

# Interaction

This is an interactive problem. Your submission will be run against an interactor, which reads from the standard output of your submission and writes to the standard input of your submission. This interaction needs to follow a specific protocol:

The interactor first sends one line with an integer $n$ ( $1 \leq n \leq 1 0 0 0 0$ ), the length of the DNA piece for which you need to compute the uniqueness.

Then, your program should make at most $2 n$ queries. Each query is made by printing one line of the form “? i $j ^ { \flat }$ ( $0 \leq i < j \leq n$ ), indicating that you want to query whether the substring starting at position $i$ and ending at position $j - 1$ occurs in the database. The interactor will respond with either “present” or “absent”, indicating whether the substring was found in the database.

When you have determined the uniqueness $x$ of the piece of DNA, print one line of the form “! $x$ ”, after which the interaction will stop. Printing the answer does not count as a query.

Make sure you flush the buffer after each write.

A testing tool is provided to help you develop your solution.

Using more than $2 n$ queries will result in a wrong answer.

![](images/4f1df1d40797a152a234c34e3b8266b1fdc7150ff42c84e1cba9aed3fa4ce22b.jpg)

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 << endl;
        tout << "query_limit=" << query_limit << endl;
    } catch (...) {
        
    }
}


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




int solve_ground_truth(int n, const vector<int>& m) {
    
    
    
    
    
    
    

    vector<pair<int, int>> intervals;
    intervals.reserve(n);
    for (int i = 0; i < n; i++) {
        int len = m[i] + 1;
        int end = i + len;
        if (end <= n) {
            intervals.push_back({i, end});
        }
    }
    
    
    sort(intervals.begin(), intervals.end(), [](const pair<int,int>& a, const pair<int,int>& b){
        if (a.second != b.second) return a.second < b.second;
        return a.first < b.first;
    });
    
    int count = 0;
    int last_end = 0;
    for (const auto& p : intervals) {
        if (p.first >= last_end) {
            count++;
            last_end = p.second;
        }
    }
    return count;
}

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

    
    int n = inf.readInt();
    vector<int> m(n);
    for (int i = 0; i < n; i++) {
        m[i] = inf.readInt();
    }

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

    
    cout << n << endl;
    log_metrics(); 

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

        
        string token;
        if (!(cin >> token)) {
            finish(_pe, "Unexpected EOF (did not receive answer)");
        }

        if (token == "!") {
            
            int user_ans;
            if (!(cin >> user_ans)) {
                finish(_pe, "Expected integer after '!'");
            }
            
            int correct_ans = solve_ground_truth(n, m);
            if (user_ans == correct_ans) {
                finish(_ok, "Correct answer " + to_string(user_ans));
            } else {
                finish(_wa, "Wrong answer. Expected " + to_string(correct_ans) + ", found " + to_string(user_ans));
            }
        } else {
            
            
            int i, j;
            if (token == "?") {
                if (!(cin >> i >> j)) {
                    finish(_pe, "Invalid query format after '?'");
                }
            } else {
                try {
                    i = stoi(token);
                } catch (...) {
                    finish(_pe, "Invalid token '" + token + "' (expected integer, '?' or '!')");
                }
                if (!(cin >> j)) {
                    finish(_pe, "Invalid query format (expected j)");
                }
            }

            
            if (i < 0 || i >= n || j <= i || j > n) {
                finish(_pe, "Invalid query indices i=" + to_string(i) + ", j=" + to_string(j) + " (N=" + to_string(n) + ")");
            }

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

            
            
            
            int len = j - i;
            if (len <= m[i]) {
                cout << "present" << endl;
            } else {
                cout << "absent" << 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 = 0;
static bool finished = false;


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


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


static int n;
static vector<int> L, R; 



void propagate() {
    bool changed = true;
    while (changed) {
        changed = false;
        
        for (int i = 0; i < n - 1; i++) {
            if (L[i+1] < L[i] - 1) {
                L[i+1] = L[i] - 1;
                changed = true;
            }
        }
        
        for (int i = n - 2; i >= 0; i--) {
            if (R[i] > R[i+1] + 1) {
                R[i] = R[i+1] + 1;
                changed = true;
            }
        }
        
        
        for(int i=0; i<n; i++) {
            if (L[i] > R[i]) L[i] = R[i]; 
            
        }
    }
}





int calc_uniqueness(const vector<int>& M) {
    vector<pair<int, int>> intervals; 
    for (int i = 0; i < n; i++) {
        
        
        if (M[i] < n - i) {
            int start = i;
            int end = i + M[i] + 1;
            
            if (end <= n) {
                intervals.push_back({end, start});
            }
        }
    }
    
    
    sort(intervals.begin(), intervals.end());
    
    int count = 0;
    int last_end = 0;
    for (auto& p : intervals) {
        if (p.second >= last_end) {
            count++;
            last_end = p.first;
        }
    }
    return count;
}

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

    
    
    n = inf.readInt(1, 10000, "n");
    
    
    
    query_limit = 2LL * n;
    
    
    cout << n << endl;
    
    
    L.resize(n, 0);
    R.resize(n);
    for (int i = 0; i < n; i++) R[i] = n - i;
    
    
    int hard_cap = max(20000, 10 * (int)query_limit);
    
    while (true) {
        string type;
        if (!(cin >> type)) {
            finish(_pe, "Unexpected EOF (contestant output ended)");
        }
        
        if (type == "!") {
            int ans;
            if (!(cin >> ans)) finish(_pe, "Expected integer after '!'");
            
            
            propagate();
            
            
            
            
            int u_max = calc_uniqueness(L);
            int u_min = calc_uniqueness(R);
            
            if (u_min == u_max) {
                if (ans == u_min) {
                    finish(_ok, "Correct uniqueness " + to_string(ans));
                } else {
                    finish(_wa, "Wrong answer. Expected " + to_string(u_min) + ", found " + to_string(ans));
                }
            } else {
                
                
                
                
                if (ans < u_min || ans > u_max) {
                    finish(_wa, "Answer " + to_string(ans) + " not in feasible range [" + to_string(u_min) + ", " + to_string(u_max) + "]");
                } else {
                    finish(_wa, "Ambiguous result. Uniqueness could be " + to_string(u_min) + " or " + to_string(u_max));
                }
            }
            break;
        } 
        else if (type == "?" || (type.size() > 0 && type[0] == '?')) {
            int i, j;
            
            if (type == "?") {
                if (!(cin >> i >> j)) finish(_pe, "Invalid query format");
            } else {
                finish(_pe, "Query token must be '?'");
            }
            
            queries++;
            
            
            if (queries > hard_cap) finish(_pe, "Hard query limit exceeded");
            
            if (i < 0 || i >= n || j <= i || j > n) {
                finish(_pe, "Query indices out of bounds");
            }
            
            int len = j - i;
            
            
            
            
            bool can_present = (R[i] >= len);
            bool can_absent = (L[i] < len);
            
            bool reply_present = false;
            
            if (!can_present && !can_absent) {
                finish(_wa, "Internal Error: Inconsistent state");
            } else if (can_present && !can_absent) {
                reply_present = true;
            } else if (!can_present && can_absent) {
                reply_present = false;
            } else {
                
                
                
                int mid = L[i] + (R[i] - L[i]) / 2;
                if (len <= mid) reply_present = true;
                else reply_present = false;
            }
            
            if (reply_present) {
                cout << "present" << endl;
                L[i] = max(L[i], len);
            } else {
                cout << "absent" << endl;
                R[i] = min(R[i], len - 1);
            }
            
            propagate();
            
            
            if (queries % 1000 == 0) log_metrics();
            
        } else {
            finish(_pe, "Unknown token '" + type + "'");
        }
    }

    return 0;
}


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

using namespace std;




vector<int> generate_M(int n, int type) {
    vector<int> m(n);
    if (type == 0) { 
        fill(m.begin(), m.end(), 0);
    } else if (type == 1) { 
        for (int i = 0; i < n; i++) m[i] = n - i;
    } else if (type == 2) { 
        m[0] = rnd.next(0, n);
        for (int i = 0; i < n - 1; i++) {
            int low = max(0, m[i] - 1);
            int high = n - (i + 1);
            m[i+1] = rnd.next(low, high);
        }
    } else if (type == 3) { 
        m[0] = rnd.next(0, n);
        for (int i = 0; i < n - 1; i++) {
            int low = max(0, m[i] - 1);
            int high = n - (i + 1);
            
            if (rnd.next(5) == 0) {
                m[i+1] = rnd.next(low, high);
            } else {
                m[i+1] = low;
            }
        }
    }
    return m;
}

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

    string mode = opt<string>("mode", "non");
    int n_arg = opt<int>("n", -1);
    
    long long seed_val = 0;
    if (argc > 1) seed_val = atoll(argv[1]);

    int n = n_arg;
    if (n == -1) {
        if (seed_val >= 1 && seed_val <= 5) n = 10;
        else if (seed_val >= 6 && seed_val <= 10) n = 100;
        else n = rnd.next(100, 10000);
    }
    
    
    if (mode == "adp") {
        cout << n << endl;
        cout << -1 << endl;
        return 0;
    }
    
    
    cout << n << endl;
    
    vector<int> m;
    if (seed_val == 1) {
        m = generate_M(n, 0); 
    } else if (seed_val == 2) {
        m = generate_M(n, 1); 
    } else if (seed_val == 3) {
        m.resize(n);
        for(int i=0; i<n; i++) m[i] = (i % 2 == 0) ? 0 : 1; 
    } else {
        
        
        int type = (rnd.next(3) == 0) ? 2 : 3;
        m = generate_M(n, type);
    }
    
    for (int i = 0; i < n; i++) {
        cout << m[i] << (i == n - 1 ? "" : " ");
    }
    cout << endl;

    return 0;
}