类别:分治
题目描述简化变形:
给你n个叶子节点,以及两两叶子节点之间的最近公共祖先的权值,让你构造一棵树,要求子节点权值严格小于当前节点权值。
思路:
如果题目变形成上述,不知道你的思路是否开阔了?
我们可以发现,树根的权值一定是最大的。叶子节点的数目只有500,因此可以暴力N^2枚举,找两个叶子节点X、Y他们的最近公共祖先的权值为当前树的最大权值节点,根据题目的隐含要求,最大权值节点一定是当前树的根节点。接着随便给这个根节点root一个编号,以X节点为路径一端,枚举另一端叶子节点Z,如果两者的最近公共祖先的权值与之前所求最大权直接点相等,说明X-Z的路径过当前树的根节点,这也说明Z节点不在X节点一侧。以是否在X节点一侧为条件进行划分,整颗树变成两棵树,接着继续上述操作,进行分治操作。
红色为节点的权值,黑色为节点编号,假设现在以节点1为端点进行枚举,可以发现如果最近公共祖先的权值为10都在绿色一侧,其他都为蓝色一侧。因此可以进行分治
#include <bits/stdc++.h>
#define pb push_back
#define ALL(x) x.begin(),x.end()
#define fio ios::sync_with_stdio(false);cin.tie(0);
const double pi = acos(-1);
const int N = 510;
using namespace std;
int n;
int a[N][N];
int num;
int fa[N*2];
int salary[N*2];
int work(vector<int>vec, int F = -1){
if(vec.size() == 1){
if(F != -1) fa[vec[0]] = F;
return vec[0];
}
int MAX = -1, z = -1;
if(F != -1) {
MAX = salary[F], z = vec[0];
vector<int>V[2];
V[0].pb(z);
for(int i = 1; i < vec.size(); ++i){
int u = vec[i];
if(MAX != a[z][u])V[0].pb(u);
else V[1].pb(u);
}
fa[work(V[0])] = F;
if(V[1].size())work(V[1], F);
return F;
}
for(int i = 0; i < vec.size(); ++i){
for(int j = i+1; j < vec.size(); ++j){
int u = vec[i], v = vec[j];
if(MAX < a[u][v]) MAX = a[u][v], z = u;
}
}
int now = ++num; //给当前树的根节点一个编号
salary[now] = MAX;//当前树根节点的权值,即为两两枚举的最大值
vector<int>V[2];
V[0].pb(z);//V[0]存放X一侧子树,V[1]存放另一侧
for(int i = 0; i < vec.size(); ++i){
if(z == vec[i]) continue;
int u = vec[i];
if(MAX != a[z][u])V[0].pb(u);
else V[1].pb(u);
}
fa[work(V[0])] = now;
if(V[1].size())work(V[1], now);
return now;
}
int main(){
fio;
cin >> n;
num = n;
vector<int>vec;
for(int i = 1; i <= n; ++i){
vec.pb(i);
for(int j = 1; j <= n; ++j){
cin >> a[i][j];
if(i == j)salary[i] = a[i][j];
}
}
int head = work(vec);
cout<<num<<endl;
for(int i = 1; i <= num; ++i)cout<<salary[i]<<" ";cout<<endl;
cout<<head<<endl;
for(int i = 1; i <= num; ++i){
if(i != head) cout<<i<<" "<<fa[i]<<endl;
}
return 0;
}