F - Lost Root CodeForces - 1061F(交互+概率)

Lost Root
time limit per test 3 seconds
memory limit per test 256 megabytes

Problem Description

The graph is called tree if it is connected and has no cycles. Suppose the tree is rooted at some vertex. Then tree is called to be perfect kk-ary tree if each vertex is either a leaf (has no children) or has exactly kk children. Also, in perfect kk-ary tree all leafs must have same depth.

For example, the picture below illustrates perfect binary tree with 1515 vertices:

There is a perfect kk-ary tree with nn nodes. The nodes are labeled with distinct integers from 11 to nn, however you don't know how nodes are labelled. Still, you want to find the label of the root of the tree.

You are allowed to make at most 60⋅n60⋅n queries of the following type:

  • "? aa bb cc", the query returns "Yes" if node with label bb lies on the path from aa to cc and "No" otherwise.

Both aa and cc are considered to be lying on the path from aa to cc.

When you are ready to report the root of the tree, print

  • "! ss", where ss is the label of the root of the tree.

It is possible to report the root only once and this query is not counted towards limit of 60⋅n60⋅n queries.

Interaction

The first line of the standard input stream contains two integers nn and kk (3≤n≤15003≤n≤1500, 2≤k<n2≤k<n) — the number of nodes in the tree and the value of kk.

It is guaranteed that nn is such that the tree forms a perfect kk-ary tree.

You can ask at most 60⋅n60⋅n queries. To ask a query, print a line of form "? aa bb cc", where 1≤a,b,c≤n1≤a,b,c≤n. After that you should read a single line containing "Yes" or "No" depending on the answer of the query.

The tree is fixed for each test and it doesn't depend on your queries.

When you are ready to print the answer, print a line of the form "! ss", where ss is the label of the root vertex and then terminate your program.

After printing each query do not forget to print end of line and flush the output. Otherwise you may 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 documentation for other languages.

In case your program will make more than 60⋅n60⋅n queries, but in other aspects would follow the interaction protocol and terminate coorectly, it will get verdict «Wrong Answer».

Hacks

To hack the solution use the following test format:

The first line should contain integers nn and kk (3≤n≤15003≤n≤1500, 2≤k≤15002≤k≤1500) — the number of vertices and the kk parameter of the tree.

Of course, the value of nn must correspond to the size of the valid kk-ary tree of some depth.

The second line should contain a1,a2,…,ana1,a2,…,an (1≤ai≤n1≤ai≤n) — the labels of the tree in the natural order, all labels must be distinct.

Let's call the following ordering of the tree vertices to be natural: first the root of the tree goes, then go all vertices on depth of one edge from root, ordered from left to right, then go all vertices on depth of two edges from root, ordered from left to right, and so on until the maximum depth.

This way, the a1a1 is the answer for the hack.

Example
Input
Copy
3 2

No

Yes
Output
Copy
? 1 3 2

? 1 2 3

! 2
Note

The tree in the example is as follows:

The input and output for example illustrate possible interaction on that test (empty lines are inserted only for clarity).

The hack corresponding to the example would look like:

3 2
2 3 1

Solution:

首先给出一个n节点的k叉树,1-n被乱贴在节点上,每次能询问 x,y,z(y是否再x,z路径上在就交互Yes,不在就No)问能否再 60*n次询问内,找到root具体贴上什么
意思是我们要在yes/no中挖掘出有利于找出根的信息,考虑yes,说明 某个数在某个路径上,就是在k叉树上不小心 根 包含在这路径上,返回yes,也得不到是否就是根,
意思是返回yes仅仅代表它存在于路径中,我们可能要借助它存在于路径的次数得出结论。出现次数究竟能干什么呢,如果某个路径包含根,肯定分在两边,我们想尽可能两边对称才好办
利用等值可以知道中点,那什么时候中点必然是根呢?很难办,如果是叶子节点的话就是路径长度为2h-1的时候了,叶子节点怎么求呢?假设y是叶子节点的话肯定没右路径可以包含它~,
利用no就行了,只要 i x y(x!=y,i∈[1,n]) 一直是no即叶子节点。
知道两个叶子节点就可以反求根了,复杂度的话,由于叶子节点每层都会增大k倍(k>=2)那至少也得有1/2的叶子节点,而找到两个不同的叶子节点后,只要其位于两边,路径肯定是2*h+1
概率也是1/2,大概 15* O(n)就找到两个叶子节点了,剩下概率很高就是了(我也不是很清楚。。。=_=!)

接下来就是代码啦~

 1 #include<iostream>
 2 #include<algorithm>
 3 #include<string>
 4 #include<cmath>
 5 
 6 using namespace std;
 7 
 8 int n, k, h;
 9 string ts;
10 
11 void query(int x, int y, int z) {
12     cout << "? " << x << " " << y << " " << z << endl;
13 }
14 
15 int getleaf() {
16     while (1) {
17         int x = rand() % n + 1,y;
18         while (y = rand() % n + 1) if (y != x) break;
19         int f = 0;
20         for (int i = 1; i <= n; ++i) {
21             if (i == x || i == y) continue;
22             query(i, x, y);
23             cin >> ts;
24             if (ts == "Yes") { f = 1; break; }
25         }
26         if (f == 0) return x;
27     }
28 }
29 
30 int cnt_path_num(int x,int y) {
31     int cnt = 0;
32     for (int i = 1; i <= n; ++i) {
33         query( x, i, y);
34         cin >> ts;
35         if (ts == "Yes") ++cnt;
36     }
37     //printf("x,y_path %d %d ,cnt: %d\n", x, y, cnt);
38     return cnt;
39 }
40 
41 int get_root(int x, int y) {
42     for (int i = 1; i <= n; ++i) {
43         query( x, i, y);
44         cin >> ts;
45         if (ts == "Yes") {
46             int l = cnt_path_num(x, i);
47             int r = cnt_path_num(i, y);
48             //printf("%d-> %d <-%d,%d,%d\n", x, i, y, l, r);
49             if (l == r && l == h) return i;
50         }
51     }
52     return 0;
53 }
54 
55 int main()
56 {
57     srand(1e9+7);
58     cin >> n >> k;
59     for (int i = 0,t=1; i <= n; t *= k, i += t, ++h);
60     int x = getleaf();
61     //printf("leaf %d\n", x);
62     while (1) {
63         int y = getleaf();
64         //printf("leaf %d\n", y);
65         if (x == y) continue;
66         if (cnt_path_num(x, y) != 2 * h - 1) continue;
67         //puts("来啦老弟\n");
68         int tmp = get_root(x, y);
69         if (tmp == 0) continue;
70         return cout << "! " << tmp << endl, 0;
71     }
72     return 0;
73 }

 

posted @ 2019-02-18 23:13  SayGB  阅读(336)  评论(0编辑  收藏  举报