1. 程式人生 > 其它 >CF1592D Hemose in ICPC ? 題解

CF1592D Hemose in ICPC ? 題解

題目傳送門

題目大意

這是一道互動題。給定一棵樹,有邊權。已知這棵樹的形態但不知道邊權。現在設 \(\operatorname{Dist}\left(u,v\right)\) 為點 \(u\)\(v\) 的簡單路徑上所有邊權的最大公約數。你現在可以給出一個點集,你可以得到 \(\operatorname{Dist}\left(u,v\right)_{max}\) 當然 \(u,v\) 是這個點集裡面的點。你要求棵樹中邊權最大的邊的兩個端點,最多詢問 \(12\) 次,樹的節點數 \(n \le10^5\)

題目解析

顯然我們最多詢問 \(\log n\) 次,不難想到是二分。
難點在於用什麼二分和怎麼寫 check

函式。
check 函式其實很好寫。我們發現如果給出的點任意兩兩之間都能通過經過點集之內的點互相到達,那麼輸出的就是這些點之間的最大邊權。然後我們也可以通過輸出所有的點來得到樹上最大的邊的邊權是多少,這樣就可以檢測這些點之中是否有最大邊了。
因此我們要構造一個恰當的序列再二分才可以,不難發現在這棵樹上的尤拉序上二分即可,這樣需要詢問 \(\log n +2\) 次 。
程式碼:

#include<cstdio>
#include<cstring>
#include<iostream>
#define I inline
#define db double
#define U unsigned
#define R register
#define ll long long
#define RI register int
#define ull unsigned long long
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
#define Me(a,b) memset(a,b,sizeof(a))
#define EPS (1e-7)
#define INF (0x7fffffff)
#define LL_INF (0x7fffffffffffffff)
#define maxn 1039
//#define debug
using namespace std;
#define Type int
I Type read(){
	Type sum=0; int flag=0; char c=getchar();
	while((c<'0'||c>'9')&&c!='-') c=getchar(); if(c=='-') c=getchar(),flag=1;
	while('0'<=c&&c<='9'){ sum=(sum<<1)+(sum<<3)+(c^48); c=getchar(); }
	if(flag) return -sum; return sum;
}
int n,u,v,maxx;
int head[maxn],nex[maxn<<1],to[maxn<<1],kkk;
#define add(x,y) nex[++kkk]=head[x]; head[x]=kkk; to[kkk]=y;
int a[maxn<<1],len;
void geta(int x,int pre){
	a[++len]=x;
	for(RI i=head[x];i;i=nex[i]) if(to[i]!=pre){
		geta(to[i],x); a[++len]=x;
	} return;
}
int t[maxn],num,l,r,mid;
int check(int x){
	Me(t,0); num=0; RI i; for(i=l;i<=mid;i++) t[a[i]]++;
	for(i=1;i<=n;i++) if(t[i]) num++; cout.flush(); cout<<"? "<<num<<' ';
	for(i=1;i<=n;i++) if(t[i]) cout<<i<<' ';
	cout<<endl; cin>>num; return num==maxx;
}
int main(){
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
	n=read(); RI i; for(i=1;i<n;i++){ cin>>u>>v; add(u,v) add(v,u) }
	cout.flush(); cout<<"? "<<n<<' '; for(i=1;i<=n;i++) cout<<i<<' ';
	cout<<endl; cin>>maxx; geta(1,-1); //for(i=1;i<=len;i++) printf("%d ",a[i]);
	l=1; r=len; while(l+1<r){ mid=(l+r)>>1; if(check(mid)) r=mid; else l=mid; }
	cout.flush(); cout<<"! "<<min(a[l],a[r])<<' '<<max(a[l],a[r])<<endl;
	return 0;
}