1. 程式人生 > 其它 >Codeforces 1129E - Legendary Tree(思維題)

Codeforces 1129E - Legendary Tree(思維題)

人類智慧思維題,直接爬了/wq

Codeforces 題面傳送門 & 洛谷題面傳送門

考慮以 \(1\) 為根,記 \(siz_i\)\(i\) 子樹的大小,那麼可以通過詢問 \(S=\{2,3,\cdots,n\},T=\{1\}\) 以及每個 \(u\) 得出 \(siz_u\) 的值。

考慮將所有點按 \(siz\) 從小到大排序並維護一個集合 \(st\) 表示目前還沒有找到父親的點的集合,那麼我們列舉到一個點 \(x\) 時就在 \(st\) 中二分找到所有父親為 \(x\) 的點並將它們從 \(st\) 中刪除,具體步驟是,我們先二分找出 \(st\) 中編號最小的點,即二分出一個 \(mid\) 後就詢問 \(S=st\)

中編號最小的 \(mid\) 個點組成的集合,\(T=\{1\}\)\(u=x\),如果互動庫返回的值 \(>0\) 就向左二分,否則向右二分,二分出第一個後再找第二個、第三個……以此類推。刪完這些點之後加入將 \(x\) 加入集合,如此進行下去即可。最後掃描到 \(1\) 時就將 \(st\) 中的點的父親全部設為 \(1\) 並清空 \(st\)

時間複雜度 \(n\log^2n\),詢問次數 \(n\log n\),實測在 \(7000\) 左右,可以通過此題的限制。

看到沒?什麼超綱的演算法都沒有。所以啊,菜是原罪/kk——Codeforces Round #691 (Div.2) 題解

const int MAXN=500;
int n,siz[MAXN+5],ord[MAXN+5];set<int> st;
bool cmp(int x,int y){return siz[x]<siz[y];}
bool check(int x,int l,int r){
	set<int>::iterator it=st.begin();
	for(int i=1;i<l;i++) ++it;printf("%d\n",r-l+1);
	for(int i=1;i<=r-l+1;i++) printf("%d%c",*it++," \n"[i==r-l+1]);
	printf("1\n1\n%d\n",x);fflush(stdout);
	int t;scanf("%d",&t);return t>0;
}
int main(){
	scanf("%d",&n);siz[1]=n;
	for(int i=2;i<=n;i++){
		printf("%d\n",n-1);
		for(int j=2;j<=n;j++) printf("%d%c",j," \n"[j==n]);
		printf("1\n1\n%d\n",i);fflush(stdout);
		scanf("%d",&siz[i]);
	} for(int i=1;i<=n;i++) ord[i]=i;sort(ord+1,ord+n+1,cmp);
	vector<pii> ans;
	for(int i=1;i<n;i++){
		if(!st.empty()){
			int cur=0;vector<int> son;
			while(cur<st.size()){
				int l=cur+1,r=st.size(),p=st.size()+1;
				while(l<=r){
					int mid=l+r>>1;
					if(check(ord[i],cur+1,mid)) p=mid,r=mid-1;
					else l=mid+1;
				} if(p!=st.size()+1){
					set<int>::iterator it=st.begin();
					for(int j=1;j<p;j++) ++it;
					son.pb(*it);
				} cur=p;
			} for(int x:son) st.erase(st.find(x)),ans.pb(mp(x,ord[i]));
		} st.insert(ord[i]);
	} printf("ANSWER\n");
	for(int x:st) ans.pb(mp(x,1));
	for(pii p:ans) printf("%d %d\n",p.fi,p.se);
	fflush(stdout);
	return 0;
}