[Ozon Tech Challenge 2020 (Div.1 + Div.2, Rated]-D. Kuroni and the Celebration(交互题+树的性质)

This is an interactive problem.

After getting AC after 13 Time Limit Exceeded verdicts on a geometry problem, Kuroni went to an Italian restaurant to celebrate this holy achievement. Unfortunately, the excess sauce disoriented him, and he's now lost!

The United States of America can be modeled as a tree (why though) with nn vertices. The tree is rooted at vertex rr, wherein lies Kuroni's hotel.

Kuroni has a phone app designed to help him in such emergency cases. To use the app, he has to input two vertices uu and vv, and it'll return a vertex ww, which is the lowest common ancestor of those two vertices.

However, since the phone's battery has been almost drained out from live-streaming Kuroni's celebration party, he could only use the app at most ⌊n2⌋⌊n2⌋ times. After that, the phone would die and there will be nothing left to help our dear friend! 😦

As the night is cold and dark, Kuroni needs to get back, so that he can reunite with his comfy bed and pillow(s). Can you help him figure out his hotel's location?

Interaction

The interaction starts with reading a single integer nn (2≤n≤10002≤n≤1000), the number of vertices of the tree.

Then you will read n−1n−1 lines, the ii-th of them has two integers xixi and yiyi (1≤xi,yi≤n1≤xi,yi≤n, xi≠yixi≠yi), denoting there is an edge connecting vertices xixi and yiyi. It is guaranteed that the edges will form a tree.

Then you can make queries of type "? u v" (1≤u,v≤n1≤u,v≤n) to find the lowest common ancestor of vertex uu and vv.

After the query, read the result ww as an integer.

In case your query is invalid or you asked more than ⌊n2⌋⌊n2⌋ queries, the program will print −1−1 and will finish interaction. You will receive a Wrong answer verdict. Make sure to exit immediately to avoid getting other verdicts.

When you find out the vertex rr, print "! rr" and quit after that. This query does not count towards the ⌊n2⌋⌊n2⌋ limit.

Note that the tree is fixed beforehand and will not change during the queries, i.e. the interactor is not adaptive.

After printing any query do not forget to print end of line and flush the output. Otherwise, you might get Idleness limit exceeded. To do this, use:

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

Hacks

To hack, use the following format:

The first line should contain two integers nn and rr (2≤n≤10002≤n≤1000, 1≤r≤n1≤r≤n), denoting the number of vertices and the vertex with Kuroni's hotel.

The ii-th of the next n−1n−1 lines should contain two integers xixi and yiyi (1≤xi,yi≤n1≤xi,yi≤n) — denoting there is an edge connecting vertex xixi and yiyi.

The edges presented should form a tree.

Example

input

Copy

6
1 4
4 2
5 3
6 3
2 3

3

4

4

output

Copy

? 5 6

? 3 1

? 1 2

! 4

Note

Note that the example interaction contains extra empty lines so that it's easier to read. The real interaction doesn't contain any empty lines and you shouldn't print any extra empty lines as well.

The image below demonstrates the tree in the sample test:

题意:

给定一个含有n个节点的有根树,你可以提出\(\lfloor \frac{n}{2} \rfloor\) 个问题,每一个问题可以问两个不同的节点\(u,v\)的最近公共祖先是哪个节点。你需要通过这些问题来确定这棵树的树根是哪个节点。

思路:

询问当前树的两个叶子节点$u,v $,答案有2种可能:

1、\(LCA(u,v)=u ||LCA(u,v)=v\),则树根一定是\(LCA(u,v)\)

2、\(LCA(u,v)!=u\)\(LCA(u,v)!=v\),那么我们可以将\(u,v\)从当前树中删除,因为没有其他节点在这两个的子树中。

我们重复此过程,直至我们发现树根或者仅剩下一个节点,那么该节点一定是树根。

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
#include <stack>
#include <map>
#include <set>
#include <vector>
#include <iomanip>
#include <sstream>
#include <bitset>
#define ALL(x) (x).begin(), (x).end()
#define sz(a) int(a.size())
#define rep(i,x,n) for(int i=x;i<n;i++)
#define repd(i,x,n) for(int i=x;i<=n;i++)
#define pii pair<int,int>
#define pll pair<long long ,long long>
#define gbtb ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define MS0(X) memset((X), 0, sizeof((X)))
#define MSC0(X) memset((X), '\0', sizeof((X)))
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define eps 1e-6
#define chu(x) cout<<"["<<#x<<" "<<(x)<<"]"<<endl
#define du3(a,b,c) scanf("%d %d %d",&(a),&(b),&(c))
#define du2(a,b) scanf("%d %d",&(a),&(b))
#define du1(a) scanf("%d",&(a));
using namespace std;
typedef long long ll;
ll gcd(ll a, ll b) {return b ? gcd(b, a % b) : a;}
ll lcm(ll a, ll b) {return a / gcd(a, b) * b;}
ll powmod(ll a, ll b, ll MOD) { if (a == 0ll) {return 0ll;} a %= MOD; ll ans = 1; while (b) {if (b & 1) {ans = ans * a % MOD;} a = a * a % MOD; b >>= 1;} return ans;}
ll poww(ll a, ll b) { if (a == 0ll) {return 0ll;} ll ans = 1; while (b) {if (b & 1) {ans = ans * a ;} a = a * a ; b >>= 1;} return ans;}
void Pv(const vector<int> &V) {int Len = sz(V); for (int i = 0; i < Len; ++i) {printf("%d", V[i] ); if (i != Len - 1) {printf(" ");} else {printf("\n");}}}
void Pvl(const vector<ll> &V) {int Len = sz(V); for (int i = 0; i < Len; ++i) {printf("%lld", V[i] ); if (i != Len - 1) {printf(" ");} else {printf("\n");}}}
inline long long readll() {long long tmp = 0, fh = 1; char c = getchar(); while (c < '0' || c > '9') {if (c == '-') fh = -1; c = getchar();} while (c >= '0' && c <= '9') tmp = tmp * 10 + c - 48, c = getchar(); return tmp * fh;}
inline int readint() {int tmp = 0, fh = 1; char c = getchar(); while (c < '0' || c > '9') {if (c == '-') fh = -1; c = getchar();} while (c >= '0' && c <= '9') tmp = tmp * 10 + c - 48, c = getchar(); return tmp * fh;}
const int maxn = 1010;
const int inf = 0x3f3f3f3f;
/*** TEMPLATE CODE * * STARTS HERE ***/
int n;
int in[maxn];
std::vector<int> son[maxn];
void build()
{
    n = readint();
    repd(i, 1, n - 1)
    {
        int x = readint();
        int y = readint();
        son[x].push_back(y);
        son[y].push_back(x);
        in[x]++;
        in[y]++;
    }
}
bool vis[maxn];
int ask(int x, int y)
{
    cout << "? " << x << " " << y << endl;
    int res;
    cin >> res;
    return res;
}
int main()
{
    //freopen("D:\\code\\text\\input.txt","r",stdin);
    //freopen("D:\\code\\text\\output.txt","w",stdout);
    build();
    int m = n / 2;
    repd(q, 1, m)
    {
        int x = -1;
        int y;
        repd(i, 1, n)
        {
            if (!vis[i] && in[i] == 1)
            {
                if (x == -1)
                {
                    x = i;
                } else
                {
                    y = i;
                    break;
                }
            }
        }
        int res = ask(x, y);
        if (res == x || res == y)
        {
            cout << "! " << res << endl;
            return 0;
        } else
        {
            vis[x] = vis[y] = 1;
            for (auto t : son[x])
            {
                in[t]--;
            }
            for (auto t : son[y])
            {
                in[t]--;
            }
        }
    }
    repd(i, 1, n)
    {
        if (!vis[i])
        {
            cout << "! " << i << endl;
        }
    }
    return 0;
}