题解 P6766 【[APIO2020]有趣的旅途】
时隔多月重见此题,发现没有题解发现其实考场上的思路是对的把重心当成根就好了()
- 思路
我们一开始并不能确定根节点是哪里所以我们先不去管它。
假设我们找到了一个根节点,并且它的度数为2,我们考虑怎么做。
一个显然的想法,我们每次从它的左右两个子树中选取深度最大的一个,因为总路程可以看成一个子树->根->另一个子树,而因为对于每一个子树里我们选取的深度都是不增的,所以一个子树->根与根->另一个子树的值都是不增的,总路程也是不增的。
现在的问题是如何找到这样的一个根。我们发现每两次操作都会使两棵子树大小各减一,而如果一个子树的大小与另一颗子树的大小相差2及以上,那么在最后就只会剩下根->子树,我们不能保证有一个合适的方案使其在路程小于等于之前的同时走的路程不增,所以最好的办法是让一个子树的大小与另一颗子树的大小相差1及以下,所以我们可以知道选择的点是重心。
以上是重心度数为2的情况下,我们可以选择重心为根,接下来考虑重心的度数是3该怎么处理。
因为重心的子树大小不会超过
而如果最大的子树大小小于剩余两个的和,那么我们每次从非上一次选的子树中选择一个最大的(第一次可任意选),这样也能保证路程递减,如图
一开始先从黄边走到第三棵子树如果接下来走蓝边的话就和度数为2的情况下一样必然是不增的,假如走红边我们可以用反证法,如果它的长度比第一棵子树里的黄边大,那么它们的长度顺序为第一棵子树黄
我们可以按照这样的方式进行选择,每次都会在最大的子树和剩下的两个子树里交替选择,而剩下的两个子树也可以交替选择,所以剩余两个子树的和
(有一处细节在代码里)
#include <vector>
#include <algorithm>
#include <queue>
using namespace std;
int hoursRequired(int X, int Y);
int attractionsBehind(int X, int Y);
std::vector<int> createFunTour(int N, int Q);
const int N = 1e5 + 10;
int siz[N], dep[N];
int b[3], tot;
std::vector<pair<int, int> >q[7];
std::vector<int>ans;
int root;
void merge()
{
while(q[2].size())
{
q[1].push_back(q[2][q[2].size() - 1]);
q[2].pop_back();
}
sort(q[1].begin(), q[1].end());
}
void br2()
{
while(q[0].size() + q[1].size())
{
pair<int, int>now = q[0][q[0].size() - 1];
q[0].pop_back();
ans.push_back(now.second);
q[0].swap(q[1]);
}
ans.push_back(root);
}
int C(int a, int b, int c)
{
return a + b + c - max(a, max(b, c));
}
int nn;
void br3()
{
if(q[0].size() == nn / 2)
{
merge();
if(q[0].size() < q[1].size())
q[0].swap(q[1]);
br2();
return;
}
int flag = 0;
while(1)
{
int to = flag;
for (int i = flag + 1; i <= 2; ++ i)
{
if(q[i][q[i].size() - 1] >= q[to][q[to].size() - 1])
to = i;
}
flag = 1;
pair<int, int>maxs = q[to][q[to].size() - 1];
ans.push_back(q[to][q[to].size() - 1].second);
q[0].swap(q[to]);
q[0].pop_back();
if(max(max(q[0].size(), q[1].size()), q[2].size()) == C(q[0].size(), q[1].size(), q[2].size()))
{
int now = 0;
if(q[0].size() < q[1].size())
q[0].swap(q[1]), now = 1;
if(q[0].size() < q[2].size())
q[0].swap(q[2]), now = (now == 0 ? 2 : now);
if(now != 0)
{
if(maxs < q[((now - 1) ^ 1) + 1][q[((now - 1) ^ 1) + 1].size() - 1])
ans.push_back(q[((now - 1) ^ 1) + 1][q[((now - 1) ^ 1) + 1].size() - 1].second), q[((now - 1) ^ 1) + 1].pop_back();
}
//此处注意,假如合并的两个子树中有一个是最近选择的,另一个子树的最大深度大于这个,那么要再选择一次,否则合并后下一次选择的深度就不能保证不增了
merge();
if(now == 0)
q[0].swap(q[1]);
br2();
return;
}
}
ans.push_back(root);
}
std::vector<int> createFunTour(int n, int Q)
{
nn = n;
for (int i = 0; i < n; ++ i)
{
siz[i] = attractionsBehind(0, i);
}
for (int i = 0; i < n; ++ i)
{
if(siz[i] > (n >> 1) && siz[i] < siz[root])
root = i;
}
for (int i = 0; i < n; ++ i)
{
if((dep[i] = hoursRequired(root, i)) == 1)
{
b[tot] = i;
++ tot;
}
}
-- tot;
for (int i = 0; i < n; ++ i)
{
if(i != root)
{
int to = 0;
for (int j = 1; j <= tot; ++ j)
{
if(hoursRequired(b[j], i) <= dep[i])
to = j;
}
q[to].push_back(make_pair(dep[i], i));
}
}
for (int i = 0; i <= tot; ++ i)
{
sort(q[i].begin(), q[i].end());
printf("%d\n", q[i][0].second);
}
if(tot == 1)
{
if(q[0].size() < q[1].size())
q[0].swap(q[1]);
br2();
}
else
{
if(q[0].size() < q[1].size())
q[0].swap(q[1]);
if(q[0].size() < q[2].size())
q[0].swap(q[2]);
br3();
}
return ans;
}