题目链接

二进制集合划分

一个比较经典的问题。

在一颗树上求任意两点距离最大的值,暴力的方法是枚举每一个点和其他点做比较,找出最大的值(超时)。这里有两种方法

  1. 定一个点1,通过二分的方法找出从点1到距离最远的点,记为点p,然后在不考虑点p的情况下再用同样的方法求出点q,
    那么点1到点p,和q的距离和就是树上任意两点的最大距离。

  2. (尚未证明,当定理用即可) 假设有n个点,我们可以做 log(n)次集合划分,第一次划分分别为 n 的二进制数从右向左数第一位为 0 和 1的情况,
    第二次划分分别为 n 的二进制数从右向左数第二位为 0 和 1的情况,以此类推一共划分 log(n)次,这种集合划分能够不遗漏的求出任意两点之间的最大距离。

#pragma GCC optimize(2)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>
#include <string>

using namespace std;

typedef long long ll;

const int Maxn = 3e5+10;
const int INF = 0x3f3f3f3f;
const int Mod = 1e9+7;

vector <int> a, b;

int main(void)
{
	ios::sync_with_stdio(false);
	cin.tie(false); cout.tie(false);
	int T;
	cin >> T;
	while(T--) {
        int n;
        ll ans = 0;
        cin >> n;
        for(int i = 0; i < 10; ++i) {
            a.clear();
            b.clear();
            for(int j = 1; j <= n; ++j) {
                if((j>>i)&1) b.push_back(j);
                else a.push_back(j);
            }
            if(a.size() == 0 || a.size() == n) break;
            cout << a.size() << " " << b.size() << " ";
            for(int k = 0; k < a.size(); ++k) cout << a[k] << " ";
            for(int k = 0; k < b.size(); ++k) cout << b[k] << " ";
            cout << endl;
            ll tmp;
            cin >> tmp;
            ans = max(ans, tmp);
        }
        cout << "-1 " << ans << endl;
	}
	return 0;
}