1. 程式人生 > 實用技巧 >【CF1370F2】The Hidden Pair (Hard Version)

【CF1370F2】The Hidden Pair (Hard Version)

題目

題目連結:https://codeforces.com/problemset/problem/1370/F2
這是一道互動題。困難版與簡單版唯一的區別是互動次數限制。

本題有多組資料。

已知一棵 \(n\) 個頂點的樹(邊集已知),其中有兩個不同的頂點被暗中做了標記。你現在需要通過若干次詢問,猜出兩個被標記頂點的編號。

一次詢問的格式為 ? c x_1 x_2 ... x_c,即代表你向互動庫請求關於 \(x_1,x_2,\cdots,x_c\)\(c\) 個點的資訊。

對於一次詢問,互動庫的返回格式為 x d,表示在詢問的集合中,到兩個被標記點的距離之和最小的點是 \(x\),這個最小值為 \(d\)

。如果有多個最小值點,\(x\) 的值可能是其中任意一個。

如果已經知曉答案,請用 ! x y 的格式來輸出你的答案,任意順序均可。在這之後,你會收到一個字串 Correct 或者 Incorrect,代表你的猜測是否正確。如果收到了 Incorrect,請立即終止程式,否則請繼續處理下一組資料。

對於每組資料,請你在不超過 \(11\) 次詢問之內給出答案。(Easy Version 是 \(14\) 次)

\(1\le t\le 10, 2\le n\le 1000\)

思路

首先詢問一遍所的點,可以得到兩個標記點的距離 \(dis\),以及位於它們路徑上的一個點。
然後以得到的這個點開始 dfs,用 vector 記錄每一個點的深度。
然後二分深度,每次詢問這個深度上的所有點。如果得到的距離是 \(dis\)

,說明較深的那一個點一定深度不小於當前二分的深度。
可以在 \(O(\log n)\leq 10\) 次詢問找到其中一個標記點。以這個標記點為根 dfs,然後詢問所有深度為 \(dis\) 的點,得到的就是另一個標記點了。
操作次數為 \(1+10+1=12\),可以過 Easy Version。
我們發現,因為第一次二分的是較深的點,它的深度一定不小於 \(\lceil\frac{dis}{2}\rceil\),這樣我們二分的區間就變成了 \([\lceil\frac{dis}{2}\rceil,\min(dis,maxdep)]\)。顯然這個的上界是 \(\frac{n}{2}\leq 500\)
,那麼此時的操作次數為 \(1+\log \frac{n}{2}+1\geq 1+9+1=11\)。可以通過 Hard Version。
時間複雜度 \(O(Qn\log n)\)

程式碼

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

const int N=1010;
int n,Q,rt,dis,res,maxd,tot,head[N];
char ch[20];
vector<int> node[N];

struct edge
{
	int next,to;
}e[N*2];

void add(int from,int to)
{
	e[++tot]=(edge){head[from],to};
	head[from]=tot;
}

void prework()
{
	for (int i=0;i<=maxd;i++) node[i].clear();
	memset(head,-1,sizeof(head));
	tot=maxd=0;
}

void dfs(int x,int fa,int dep)
{
	maxd=max(maxd,dep);
	node[dep].push_back(x);
	for (int i=head[x];~i;i=e[i].next)
		if (e[i].to!=fa) dfs(e[i].to,x,dep+1);
}

int main()
{
	scanf("%d",&Q);
	while (Q--)
	{
		prework();
		scanf("%d",&n);
		for (int i=1,x,y;i<n;i++)
		{
			scanf("%d%d",&x,&y);
			add(x,y); add(y,x);
		}
		
		// -----------------------------------------------------------
		printf("? %d",n);
		for (int i=1;i<=n;i++)
			printf(" %d",i);
		printf("\n");
		fflush(stdout);
		// -----------------------------------------------------------
		
		scanf("%d%d",&rt,&dis);
		dfs(rt,0,0);
		int l=dis/2,r=min(maxd,dis),mid,x,y;
		while (l<=r)
		{
			mid=(l+r)>>1;
			
			// -----------------------------------------------------------
			printf("? %d",node[mid].size());
			for (int i=0;i<node[mid].size();i++)
				printf(" %d",node[mid][i]);
			printf("\n");
			fflush(stdout);
			// -----------------------------------------------------------
			
			scanf("%d%d",&x,&y);
			if (y==dis) l=mid+1,res=x;
				else r=mid-1;
		}
		for (int i=0;i<=maxd;i++) node[i].clear();
		dfs(res,0,0);
		
		// -----------------------------------------------------------
		printf("? %d",node[dis].size());
		for (int i=0;i<node[dis].size();i++)
			printf(" %d",node[dis][i]);
		printf("\n");
		fflush(stdout);
		// -----------------------------------------------------------
		
		scanf("%d%d",&x,&y);
		printf("! %d %d\n",x,res);
		fflush(stdout);
		scanf("%s",ch);
	}
	return 0;
}