1. 程式人生 > 其它 >【luogu CF1370F】The Hidden Pair(構造)

【luogu CF1370F】The Hidden Pair(構造)

The Hidden Pair

題目連結:luogu CF1370F1 / luogu CF1370F2

題目大意

給你一棵樹,然後你要猜兩個特殊點。
每次你可以詢問一個點集,會告訴你這個點集中到兩個特殊點距離之和最近的點以及這個距離和。
然後要你在至多 \(11\) 次操作猜出這兩個點。
\(n\leq 1000\)

思路

首先看到這個操作次數跟 \(n\) 的關係不難猜到會用 \(\log\) 次左右。
然後你不難想到一開始要選全圖問一次,求出這兩個特殊點之間的路徑長度 \(d\)

那第一次給的點有什麼用呢?你會發現它是兩個特殊點之間的路徑上的一個點。
然後我們進而能發現如果一個點在這個路徑上,那它到這兩個點的路徑長度就是這兩個點之間的長度,否則就會要大於它。

然後我們考慮能不能求出其中的一個點。(因為你找到之後把距離它為 \(d\) 的點都詢問一次,得到的肯定是另一個點,畢竟別的點必定不在路徑上)
考慮一個神奇的方法就是二分。

你考慮把你第一次給的點當做根,然後去找那個深度較大的點。
那你會發現你如果二分一個深度,每次詢問把這個深度的所有點都拿去詢問,那如果得到的點的距離和是 \(d\),也就是說這個深度還有點在兩個點的路徑中,否則就沒了。
那我們找到最大的深度的時候,這個點必定是路徑其中的一個端點。

然後我們看看操作次數:
一開始一次,中間 \(\log\) 次,最後一次:\(1+10+1=12\) 剛好超了一個。
(這個時候簡單版已經能過了)

那怎麼優化呢?其實有個小小的性質,就是在二分的時候,你下界 \(l\)

不一定要從 \(0\) 開始。
畢竟你路徑長度為 \(d\),你還要找深度大的點,那它至少的深度會在 \(\left\lceil\frac{d}{2}\right\rceil\),那把 \(l\) 一開始弄成這個,就剛好少了一次操作!

程式碼

#include<cstdio>
#include<vector>
#define clean() fflush(stdout)

using namespace std;

const int N = 1000 + 10;
int TT, n, x, y, d, deg[N], S, T;
vector <int> G[N], ds[N];
char s[11];

void dfs(int now, int father) {
	ds[deg[now]].push_back(now);
	for (int i = 0; i < G[now].size(); i++) {
		int x = G[now][i]; if (x == father) continue;
		deg[x] = deg[now] + 1; dfs(x, now);
	}
}

bool check(int x) {
	if (!ds[x].size()) return 0;
	printf("? %d", ds[x].size()); for (int i = 0; i < ds[x].size(); i++) printf(" %d", ds[x][i]); printf("\n"); clean();
	int rx, ry;
	scanf("%d %d", &rx, &ry);
	if (ry == d) {S = rx; return 1;}
	return 0;
}

int main() {
	scanf("%d", &TT);
	while (TT--) {
		scanf("%d", &n);
		for (int i = 1; i < n; i++) {
			scanf("%d %d", &x, &y); G[x].push_back(y); G[y].push_back(x);
		}
		
		printf("? %d", n); for (int i = 1; i <= n; i++) printf(" %d", i); printf("\n"); clean();
		scanf("%d %d", &x, &d);
		
		for (int i = 1; i <= n; i++) ds[i].clear();
		deg[x] = 0; dfs(x, 0);
		
		int l = (d + 1) / 2, r = d;
		while (l <= r) {
			int mid = (l + r) >> 1;
			if (check(mid)) l = mid + 1;
				else r = mid - 1;
		}
		
		for (int i = 1; i <= n; i++) ds[i].clear();
		deg[S] = 0; dfs(S, 0);
		printf("? %d", ds[d].size()); for (int i = 0; i < ds[d].size(); i++) printf(" %d", ds[d][i]); printf("\n"); clean();
		int tmp; scanf("%d %d", &T, &tmp);
		
		printf("! %d %d\n", S, T); clean();
		scanf("%s", s + 1); if (s[1] == 'I') return 0;
		
		for (int i = 1; i <= n; i++) G[i].clear();
	}
	
	return 0;
}