• Solution -「CF 1370F2」The Hidden Pair (Hard Version)


    (mathcal{Description})

      Link (hard) & Link (easy).

      这是一道交互题。

      给定一棵 (n) 个结点的树,其中有两个是特殊结点。每次你可以提出形如 (x~c_1~c_2~cdots~c_x) 的询问,交互器会回答在点集 ({c_x}) 中,到两个特殊结点距离之和最小的结点 (p) 和这个最小距离和 (d)(若有多个 (d),回答任意一个)。你需要猜出两个特殊结点的编号。

      (nle10^3)(Tle10) 组数据,询问次数上限为 (11) 次。

    (mathcal{Solution})

      第一次询问,显然问所有的 (n) 个点,就能得到特殊点 (x,y) 间的距离 (d)(x,y) 路径上的一个结点 (p)

      以 (p) 为根,(x,y) 显然在 (p) 的两棵子树内。接下来,以 (p) 为圆心“画圆”——询问所有到 (p) 的距离为某一定值 (l) 的点集,就能得到一个新的距离 (d')。若 (d'=d),说明 (x,y)(p) 较远的一个点到 (p) 的距离 (ge d'),否则,就 (<d'),所以可以二分 (l),花 (mathcal O(log n)) 次询问找到离 (p) 较远的那个特殊点。

      最后,设较远点 (x)(p) 的距离为 (l),把到 (p) 距离为 (d-l) 的所有点拿出来再问一次就得到另一个特殊点 (y) 了(注意排除掉在 (p)(x) 路径上的点)。

      但是,最坏情况会有 (1+lceillog_210^5 ceil+1=12) 次询问,刚好多一次 qwq。

      不过二分找到是“较远点”,所以其到 (p) 的距离一定在 ([lfloorfrac{d}2 floor,d]) 之间,取这个区间作为二分上下界。其大小显然不超过 (frac{n}2),所以刚好能节约一次二分的询问。

      复杂度 (mathcal O(Tn))

    (mathcal{Code})

    /* Clearink */
    
    #include <cstdio>
    #include <vector>
    #include <assert.h>
    
    inline int rint () {
    	int x = 0, f = 1; char s = getchar ();
    	for ( ; s < '0' || '9' < s; s = getchar () ) f = s == '-' ? -f : f;
    	for ( ; '0' <= s && s <= '9'; s = getchar () ) x = x * 10 + ( s ^ '0' );
    	return x * f;
    }
    
    const int MAXN = 1000;
    int n, ecnt, mxd, head[MAXN + 5], fa[MAXN + 5], dep[MAXN + 5];
    std::vector<int> all, eqdis[MAXN + 5];
    bool ban[MAXN + 5];
    
    struct Edge { int to, nxt; } graph[MAXN * 2 + 5];
    
    inline void link ( const int s, const int t ) {
    	graph[++ ecnt] = { t, head[s] };
    	head[s] = ecnt;
    }
    
    inline void collect ( const int u, const int d ) {
    	eqdis[dep[u] = d].push_back ( u ), mxd = d < mxd ? mxd : d;
    	for ( int i = head[u], v; i; i = graph[i].nxt ) {
    		if ( ( v = graph[i].to ) ^ fa[u] ) {
    			fa[v] = u, collect ( v, d + 1 );
    		}
    	}
    }
    
    inline void inter ( const std::vector<int>& pts, int& p, int& dis ) {
    	if ( pts.empty () ) return void ( p = dis = 0 );
    	printf ( "? %d", ( int ) pts.size () );
    	for ( int u: pts ) printf ( " %d", u );
    	putchar ( '
    ' ), fflush ( stdout );
    	assert ( ~( p = rint (), dis = rint () ) );
    }
    
    inline void clear () {
    	ecnt = mxd = 0, all.clear (), eqdis[0].clear ();
    	for ( int i = 1; i <= n; ++ i ) {
    		head[i] = dep[i] = fa[i] = ban[i] = 0;
    		eqdis[i].clear ();
    	}
    }
    
    int main () {
    	char rep[20];
    	for ( int T = rint (); T --; ) {
    		clear ();
    		n = rint ();
    		for ( int i = 1, u, v; i < n; ++ i ) {
    			all.push_back ( i );
    			u = rint (), v = rint ();
    			link ( u, v ), link ( v, u );
    		}
    		int p, dis;
    		all.push_back ( n ), inter ( all, p, dis );
    		collect ( p, 0 );
    		int l = dis + 1 >> 1, r = mxd < dis ? mxd : dis, S = 0;
    		while ( l < r ) {
    			int mid = l + r + 1 >> 1, curp, curd;
    			inter ( eqdis[mid], curp, curd );
    			if ( curd > dis ) r = mid - 1;
    			else S = curp, l = mid;
    		}
    		if ( !S ) inter ( eqdis[l], S, r );
    		int oth = dis - dep[S], Q, tmp;
    		for ( int u = S; u ^ p; u = fa[u] ) ban[u] = true;
    		for ( auto it ( eqdis[oth].begin () ); it != eqdis[oth].end (); ++ it ) {
    			if ( ban[*it] ) {
    				eqdis[oth].erase ( it );
    				break;
    			}
    		}
    		inter ( eqdis[oth], Q, tmp );
    		printf ( "! %d %d
    ", S, Q ), fflush ( stdout );
    		scanf ( "%s", rep ), assert ( rep[0] == 'C' );
    	}
    	return 0;
    }
    

    (mathcal{Details})

      一眼出 (12) 次询问的方法然后卡了半天 qwq……养成卡二分上下界的习惯对常数有极大好处。(

  • 相关阅读:
    最大最小值得判断代码
    等腰三角形的代码及各类代码
    Java ArrayList和Vector、LinkedList与ArrayList、数组(Array)和列表集合(ArrayList)的区别
    Java 集合类的特性
    Java 用程序给出随便大小的10 个数,序号为1-10,按从小到大顺序输出,并输出相应的序号?
    List、Map、Set三个接口,存取元素时,各有什么特点?
    Java 清除数组相同元素
    eclipse导入项目出现叹号处理方法:
    初学者-PHP笔记
    java 对象输入输出流
  • 原文地址:https://www.cnblogs.com/rainybunny/p/13693496.html
Copyright © 2020-2023  润新知