Problem: cf916_d
Difficulty: medium
Categories: graph, data structures
Time limit: 2000 ms, memory limit: 512 MB

D. Jamie and To-do List

Why I have to finish so many assignments???

Jamie is getting very busy with his school life. He starts to forget the assignments that he has to do. He decided to write the things down on a to-do list. He assigns a value priority for each of his assignment (lower value means more important) so he can decide which he needs to spend more time on.

After a few days, Jamie finds out the list is too large that he can't even manage the list by himself! As you are a good friend of Jamie, help him write a program to support the following operations on the to-do list:

* *set* *a**i* *x**i* — Add assignment *a**i* to the to-do list if it is not present, and set its priority to *x**i*. If assignment *a**i* is already in the to-do list, its priority is changed to *x**i*.
* *remove* *a**i* — Remove assignment *a**i* from the to-do list if it is present in it.
* *query* *a**i* — Output the number of assignments that are more important (have a smaller priority value) than assignment *a**i*, so Jamie can decide a better schedule. Output  - 1 if *a**i* is not in the to-do list.
* *undo* *d**i* — Undo all changes that have been made in the previous *d**i* days (not including the day of this operation)

At day 0, the to-do list is empty. In each of the following *q* days, Jamie will do exactly one out of the four operations. If the operation is a *query*, you should output the result of the query before proceeding to the next day, or poor Jamie cannot make appropriate decisions.

Input

The first line consists of a single integer *q* (1 ≤ *q* ≤ 105) — the number of operations.

The following *q* lines consists of the description of the operations. The *i*-th line consists of the operation that Jamie has done in the *i*-th day. The query has the following format:

The first word in the line indicates the type of operation. It must be one of the following four: set, remove, query, undo.

* If it is a set operation, a string *a**i* and an integer *x**i* follows (1 ≤ *x**i* ≤ 109). *a**i* is the assignment that need to be set to priority *x**i*.
* If it is a remove operation, a string *a**i* follows. *a**i* is the assignment that need to be removed.
* If it is a query operation, a string *a**i* follows. *a**i* is the assignment that needs to be queried.
* If it is a undo operation, an integer *d**i* follows (0 ≤ *d**i* < *i*). *d**i* is the number of days that changes needed to be undone.

All assignment names *a**i* only consists of lowercase English letters and have a length 1 ≤ |*a**i*| ≤ 15.

It is guaranteed that the last operation is a query operation.

Output

For each query operation, output a single integer — the number of assignments that have a priority lower than assignment *a**i*, or  - 1 if *a**i* is not in the to-do list.

Interaction

If the operation is a *query*, you should output the result of the query and flush the output stream before proceeding to the next operation. Otherwise, you may get the verdict Idleness Limit Exceed.

For flushing the output stream, please refer to the documentation of your chosen programming language. The flush functions of some common programming languages are listed below:

* C: fflush(stdout);
* C++: cout « flush;
* Java: System.out.flush();

Examples

Input

```
8  
set chemlabreport 1  
set physicsexercise 2  
set chinesemockexam 3  
query physicsexercise  
query chinesemockexam  
remove physicsexercise  
query physicsexercise  
query chinesemockexam
```

Output

```
1  
2  
-1  
1
```

Input

```
8  
set physicsexercise 2  
set chinesemockexam 3  
set physicsexercise 1  
query physicsexercise  
query chinesemockexam  
undo 4  
query physicsexercise  
query chinesemockexam
```

Output

```
0  
1  
0  
-1
```

Input

```
5  
query economicsessay  
remove economicsessay  
query economicsessay  
undo 2  
query economicsessay
```

Output

```
-1  
-1  
-1
```

Input

```
5  
set economicsessay 1  
remove economicsessay  
undo 1  
undo 1  
query economicsessay
```

Output

```
-1
```

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


const int MAX_NODES = 12000000;
const int MAXQ = 100005;


struct Node {
    int l, r;
    int val; 
} tree[MAX_NODES];
int nodes_ptr = 0;

int new_node() {
    return ++nodes_ptr;
}

int copy_node(int src) {
    int id = ++nodes_ptr;
    tree[id] = tree[src];
    return id;
}


int build(int tl, int tr) {
    int id = new_node();
    tree[id].val = 0;
    if (tl == tr) return id;
    int tm = (tl + tr) / 2;
    tree[id].l = build(tl, tm);
    tree[id].r = build(tm + 1, tr);
    return id;
}


int update_cnt(int old_id, int tl, int tr, int pos, int diff) {
    int id = copy_node(old_id);
    if (tl == tr) {
        tree[id].val += diff;
        return id;
    }
    int tm = (tl + tr) / 2;
    if (pos <= tm) tree[id].l = update_cnt(tree[old_id].l, tl, tm, pos, diff);
    else tree[id].r = update_cnt(tree[old_id].r, tm + 1, tr, pos, diff);
    tree[id].val = tree[tree[id].l].val + tree[tree[id].r].val;
    return id;
}


int update_val(int old_id, int tl, int tr, int pos, int v) {
    int id = copy_node(old_id);
    if (tl == tr) {
        tree[id].val = v;
        return id;
    }
    int tm = (tl + tr) / 2;
    if (pos <= tm) tree[id].l = update_val(tree[old_id].l, tl, tm, pos, v);
    else tree[id].r = update_val(tree[old_id].r, tm + 1, tr, pos, v);
    
    return id;
}


int query_cnt(int id, int tl, int tr, int ql, int qr) {
    if (ql > qr) return 0;
    if (ql == tl && qr == tr) return tree[id].val;
    int tm = (tl + tr) / 2;
    return query_cnt(tree[id].l, tl, tm, ql, min(qr, tm)) +
           query_cnt(tree[id].r, tm + 1, tr, max(ql, tm + 1), qr);
}


int query_val(int id, int tl, int tr, int pos) {
    if (tl == tr) return tree[id].val;
    int tm = (tl + tr) / 2;
    if (pos <= tm) return query_val(tree[id].l, tl, tm, pos);
    else return query_val(tree[id].r, tm + 1, tr, pos);
}


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());
}

struct Op {
    string type;
    string s;
    int x;
    int d;
};


int roots_cnt[MAXQ];
int roots_val[MAXQ];


map<string, int> str_to_id;
vector<int> distinct_priorities;

int get_prio_rank(int p) {
    return lower_bound(distinct_priorities.begin(), distinct_priorities.end(), p) - distinct_priorities.begin() + 1;
}

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

    int Q = inf.readInt();
    vector<Op> ops(Q);
    
    
    for (int i = 0; i < Q; ++i) {
        ops[i].type = inf.readToken();
        if (ops[i].type == "set") {
            ops[i].s = inf.readToken();
            ops[i].x = inf.readInt();
            str_to_id[ops[i].s] = 0; 
            distinct_priorities.push_back(ops[i].x);
        } else if (ops[i].type == "remove") {
            ops[i].s = inf.readToken();
            str_to_id[ops[i].s] = 0;
        } else if (ops[i].type == "query") {
            ops[i].s = inf.readToken();
            str_to_id[ops[i].s] = 0;
            query_limit++; 
        } else if (ops[i].type == "undo") {
            ops[i].d = inf.readInt();
        }
    }

    
    int M = 0;
    for (auto& kv : str_to_id) {
        kv.second = ++M;
    }
    if (M == 0) M = 1;

    
    sort(distinct_priorities.begin(), distinct_priorities.end());
    distinct_priorities.erase(unique(distinct_priorities.begin(), distinct_priorities.end()), distinct_priorities.end());
    int K = distinct_priorities.size();
    if (K == 0) K = 1;

    
    roots_cnt[0] = build(1, K);
    roots_val[0] = build(1, M);

    
    log_metrics();

    
    cout << Q << endl;

    for (int i = 1; i <= Q; ++i) {
        const auto& op = ops[i-1];

        
        if (op.type == "set") cout << "set " << op.s << " " << op.x << endl;
        else if (op.type == "remove") cout << "remove " << op.s << endl;
        else if (op.type == "query") cout << "query " << op.s << endl;
        else if (op.type == "undo") cout << "undo " << op.d << endl;

        
        if (op.type == "undo") {
            
            
            
            int target_idx = (i - 1) - op.d;
            roots_cnt[i] = roots_cnt[target_idx];
            roots_val[i] = roots_val[target_idx];
        } else {
            
            roots_cnt[i] = roots_cnt[i-1];
            roots_val[i] = roots_val[i-1];

            if (op.type == "set") {
                int sid = str_to_id[op.s];
                int pid = get_prio_rank(op.x);
                
                
                int old_pid = query_val(roots_val[i], 1, M, sid);
                if (old_pid != 0) {
                    
                    roots_cnt[i] = update_cnt(roots_cnt[i], 1, K, old_pid, -1);
                }
                
                roots_cnt[i] = update_cnt(roots_cnt[i], 1, K, pid, 1);
                
                roots_val[i] = update_val(roots_val[i], 1, M, sid, pid);

            } else if (op.type == "remove") {
                int sid = str_to_id[op.s];
                int old_pid = query_val(roots_val[i], 1, M, sid);
                
                if (old_pid != 0) {
                    roots_cnt[i] = update_cnt(roots_cnt[i], 1, K, old_pid, -1);
                    roots_val[i] = update_val(roots_val[i], 1, M, sid, 0);
                }
                

            } else if (op.type == "query") {
                
                int sid = str_to_id[op.s];
                int current_pid = query_val(roots_val[i], 1, M, sid);
                
                int expected = -1;
                if (current_pid != 0) {
                    
                    expected = query_cnt(roots_cnt[i], 1, K, 1, current_pid - 1);
                }

                int user_ans;
                
                if (!(cin >> user_ans)) {
                    finish(_pe, "Expected integer answer for query");
                }
                queries++;

                if (user_ans != expected) {
                    finish(_wa, format("Query #%d ('%s'): expected %d, got %d", i, op.s.c_str(), expected, user_ans));
                }

                if (queries % 50 == 0) log_metrics();
            }
        }
    }

    finish(_ok, "All queries correct");
}


Adaptive interactor (C++):







#include "testlib.h"
#include <bits/stdc++.h>
using namespace std;




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

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());
}






const int MAX_NODES = 6000000; 

struct Node {
    int lc, rc;
    int val; 
} nodes[MAX_NODES];

int node_ptr = 0;

int new_node() {
    int id = ++node_ptr;
    nodes[id] = {0, 0, 0};
    return id;
}

int copy_node(int u) {
    int v = ++node_ptr;
    nodes[v] = nodes[u];
    return v;
}


int update_map(int u, int l, int r, int pos, int val) {
    int v = (u == 0 ? new_node() : copy_node(u));
    if (l == r) {
        nodes[v].val = val;
        return v;
    }
    int mid = l + (r - l) / 2;
    if (pos <= mid) nodes[v].lc = update_map(nodes[u].lc, l, mid, pos, val);
    else nodes[v].rc = update_map(nodes[u].rc, mid + 1, r, pos, val);
    return v;
}


int query_map(int u, int l, int r, int pos) {
    if (!u) return 0;
    if (l == r) return nodes[u].val;
    int mid = l + (r - l) / 2;
    if (pos <= mid) return query_map(nodes[u].lc, l, mid, pos);
    else return query_map(nodes[u].rc, mid + 1, r, pos);
}


int update_cnt(int u, int l, int r, int pos, int diff) {
    int v = (u == 0 ? new_node() : copy_node(u));
    if (l == r) {
        nodes[v].val += diff;
        return v;
    }
    int mid = l + (r - l) / 2;
    if (pos <= mid) nodes[v].lc = update_cnt(nodes[u].lc, l, mid, pos, diff);
    else nodes[v].rc = update_cnt(nodes[u].rc, mid + 1, r, pos, diff);
    
    nodes[v].val = (nodes[v].lc ? nodes[nodes[v].lc].val : 0) + 
                   (nodes[v].rc ? nodes[nodes[v].rc].val : 0);
    return v;
}


int query_cnt(int u, int l, int r, int ql, int qr) {
    if (!u || l > qr || r < ql) return 0;
    if (l >= ql && r <= qr) return nodes[u].val;
    int mid = l + (r - l) / 2;
    return query_cnt(nodes[u].lc, l, mid, ql, qr) + 
           query_cnt(nodes[u].rc, mid + 1, r, ql, qr);
}





struct Op {
    string type;
    string arg_s;
    int arg_x;
    int arg_d;
};




int root_map[100005];
int root_cnt[100005];

int main(int argc, char** argv) {
    registerInteraction(argc, argv);
    atexit(log_metrics);
    ios::sync_with_stdio(false);
    cin.tie(NULL);

    int Q = inf.readInt();
    query_limit = Q;
    
    
    cout << Q << endl;

    vector<Op> ops(Q);
    vector<string> all_strings;
    vector<int> all_prio;

    
    for (int i = 0; i < Q; ++i) {
        ops[i].type = inf.readToken();
        if (ops[i].type == "set") {
            ops[i].arg_s = inf.readToken();
            ops[i].arg_x = inf.readInt();
            all_strings.push_back(ops[i].arg_s);
            all_prio.push_back(ops[i].arg_x);
        } else if (ops[i].type == "remove") {
            ops[i].arg_s = inf.readToken();
            all_strings.push_back(ops[i].arg_s);
        } else if (ops[i].type == "query") {
            ops[i].arg_s = inf.readToken();
            all_strings.push_back(ops[i].arg_s);
        } else if (ops[i].type == "undo") {
            ops[i].arg_d = inf.readInt();
        }
    }

    
    sort(all_strings.begin(), all_strings.end());
    all_strings.erase(unique(all_strings.begin(), all_strings.end()), all_strings.end());
    
    sort(all_prio.begin(), all_prio.end());
    all_prio.erase(unique(all_prio.begin(), all_prio.end()), all_prio.end());

    int num_str = (int)all_strings.size();
    int num_prio = (int)all_prio.size();

    
    auto get_str_id = [&](const string& s) {
        return (int)(lower_bound(all_strings.begin(), all_strings.end(), s) - all_strings.begin()) + 1;
    };
    auto get_prio_id = [&](int p) {
        return (int)(lower_bound(all_prio.begin(), all_prio.end(), p) - all_prio.begin()) + 1;
    };

    
    root_map[0] = 0;
    root_cnt[0] = 0;

    log_metrics();

    for (int i = 0; i < Q; ++i) {
        
        if (ops[i].type == "set") {
            cout << "set " << ops[i].arg_s << " " << ops[i].arg_x << endl;
        } else if (ops[i].type == "remove") {
            cout << "remove " << ops[i].arg_s << endl;
        } else if (ops[i].type == "query") {
            cout << "query " << ops[i].arg_s << endl;
        } else if (ops[i].type == "undo") {
            cout << "undo " << ops[i].arg_d << endl;
        }

        
        
        
        int r_map = root_map[i];
        int r_cnt = root_cnt[i];

        if (ops[i].type == "set") {
            int sid = get_str_id(ops[i].arg_s);
            int pid = get_prio_id(ops[i].arg_x);
            int old_raw = query_map(r_map, 1, num_str, sid);
            
            
            if (old_raw != 0) {
                int old_pid = get_prio_id(old_raw);
                r_cnt = update_cnt(r_cnt, 1, num_prio, old_pid, -1);
            }
            
            r_cnt = update_cnt(r_cnt, 1, num_prio, pid, 1);
            
            r_map = update_map(r_map, 1, num_str, sid, ops[i].arg_x);
            
            root_map[i+1] = r_map;
            root_cnt[i+1] = r_cnt;

        } else if (ops[i].type == "remove") {
            int sid = get_str_id(ops[i].arg_s);
            int old_raw = query_map(r_map, 1, num_str, sid);
            
            if (old_raw != 0) {
                int old_pid = get_prio_id(old_raw);
                r_cnt = update_cnt(r_cnt, 1, num_prio, old_pid, -1);
                r_map = update_map(r_map, 1, num_str, sid, 0); 
            }
            root_map[i+1] = r_map;
            root_cnt[i+1] = r_cnt;

        } else if (ops[i].type == "undo") {
            int d = ops[i].arg_d;
            
            
            
            
            root_map[i+1] = root_map[i-d];
            root_cnt[i+1] = root_cnt[i-d];

        } else if (ops[i].type == "query") {
            
            root_map[i+1] = r_map;
            root_cnt[i+1] = r_cnt;

            
            long long user_ans;
            if (!(cin >> user_ans)) {
                finish(_pe, "Expected integer answer from contestant");
            }

            int sid = get_str_id(ops[i].arg_s);
            int raw_val = query_map(r_map, 1, num_str, sid);
            long long expected = -1;
            
            if (raw_val != 0) {
                int pid = get_prio_id(raw_val);
                
                if (pid == 1) expected = 0;
                else expected = query_cnt(r_cnt, 1, num_prio, 1, pid - 1);
            }

            if (user_ans != expected) {
                finish(_wa, "Day " + to_string(i+1) + " query " + ops[i].arg_s + 
                       ": expected " + to_string(expected) + ", got " + to_string(user_ans));
            }
        }

        queries++;
        if (i % 5000 == 0) log_metrics();
    }

    finish(_ok, "Passed " + to_string(Q) + " operations");
    return 0;
}


Test generator (C++):












#include "testlib.h"
#include <bits/stdc++.h>
using namespace std;


string gen_str(int len = -1) {
    if (len == -1) len = rnd.next(1, 15);
    string s = "";
    for (int i = 0; i < len; ++i) s += (char)rnd.next('a', 'z');
    return s;
}

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

    string mode = opt<string>("mode", "non");
    long long seed_val = atoll(argv[1]);
    int q_opt = opt<int>("q", -1);

    
    if (seed_val == 1) {
        cout << "8\n";
        cout << "set chemlabreport 1\n";
        cout << "set physicsexercise 2\n";
        cout << "set chinesemockexam 3\n";
        cout << "query physicsexercise\n";
        cout << "query chinesemockexam\n";
        cout << "remove physicsexercise\n";
        cout << "query physicsexercise\n";
        cout << "query chinesemockexam\n";
        return 0;
    }
    if (seed_val == 2) {
        cout << "8\n";
        cout << "set physicsexercise 2\n";
        cout << "set chinesemockexam 3\n";
        cout << "set physicsexercise 1\n";
        cout << "query physicsexercise\n";
        cout << "query chinesemockexam\n";
        cout << "undo 4\n";
        cout << "query physicsexercise\n";
        cout << "query chinesemockexam\n";
        return 0;
    }
    if (seed_val == 3) {
        
        cout << "5\n";
        cout << "set a 1\n";
        cout << "undo 1\n"; 
        cout << "set b 2\n";
        cout << "undo 2\n"; 
        cout << "query a\n";
        return 0;
    }

    
    int Q = 100000;
    if (q_opt != -1) Q = q_opt;
    else if (seed_val <= 10) Q = 20; 

    cout << Q << "\n";

    
    
    vector<string> pool;
    int pool_size;
    if (mode == "adp") pool_size = 200; 
    else pool_size = max(1, Q / 5);

    for (int i = 0; i < pool_size; ++i) pool.push_back(gen_str());

    
    for (int i = 1; i <= Q; ++i) {
        
        if (i == Q) {
            cout << "query " << pool[rnd.next(pool_size)] << "\n";
            continue;
        }

        int type = 0; 
        if (mode == "adp") {
            
            int p = rnd.next(100);
            if (p < 30) type = 0;      
            else if (p < 50) type = 1; 
            else if (p < 80) type = 3; 
            else type = 2;             
        } else {
            
            int p = rnd.next(100);
            if (p < 35) type = 0;      
            else if (p < 50) type = 1; 
            else if (p < 90) type = 2; 
            else type = 3;             
        }

        if (type == 0) { 
            string s = pool[rnd.next(pool_size)];
            int x = rnd.next(1, 1000000000);
            cout << "set " << s << " " << x << "\n";
        } else if (type == 1) { 
            string s = pool[rnd.next(pool_size)];
            cout << "remove " << s << "\n";
        } else if (type == 2) { 
            string s = pool[rnd.next(pool_size)];
            cout << "query " << s << "\n";
        } else { 
            
            int limit = i - 1;
            if (limit == 0) {
                cout << "undo 0\n";
            } else {
                int d;
                if (mode == "adp") {
                    
                    if (rnd.next(2)) d = rnd.next(max(0, limit - 500), limit); 
                    else d = rnd.next(0, min(limit, 10)); 
                } else {
                    d = rnd.next(0, limit);
                }
                cout << "undo " << d << "\n";
            }
        }
    }

    return 0;
}