题解:P11051 [IOI2024] 树上代价
这题部分分设置很好,跟着部分分一点点想就出来了。并且代码难度并不复杂。
本文将从
本文我们对题目稍微修改,定义修改操作为,每个点花
首先对于叶子有个显然的结论,为了使得总花费最少,每个叶子的初始权值肯定是
sub1
该点特殊性质为
也就是说在满足子树内情况时,操作次数越少越好,因为到祖先操作会更优。
所以只需要
sub4
该点性质为
再次观察整个问题,当
现在我们考虑根节点的操作,如果根节点总和没到
否则我们一定是在根节点操作若干次使得根节点的总和为
为了得到总操作次数最少,我们先考虑一个必要条件。记叶子个数为
所以对于这个子任务我们只需统计叶子个数,然后答案就是
sub5
这个子任务我觉得是最重要的一个。
该子任务限制为
那么我们就来思考多了
首先,我们就不能贪心的选择在父亲操作,但是由于这些点是
那么对于
但现在求答案的方式就变了,因为我们会有若干个不同叶子个数,这个子任务到是还好,本质不同的叶子个数应该只有
我们现在就来考虑求答案如何优化,我们现在相当于求
无约束条件
在做完前面三个子任务后,最后一步已经不是很难了,只需要对做法进行优化。
我们只需考虑每一个
所以我们现在的问题就是对于每个
但是有
现在我们考虑如何求出
而如果当前最小的
不过正着删点并不好维护,我们考虑倒着往里面加点,这样就可以用并查集动态维护当前的连通块,及其叶子个数,同时用差分可以统计每个叶子个数的子树出现的总个数,具体细节请参考代码。
最后别忘了加上初始叶子的贡献。
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
int n, q, w[N], id[N], fa[N], fat[N], vis[N], sz[N];
long long sum[N], sum2[N], sumleaf;
vector<int> e[N];
bool cmp(int x, int y)
{
return w[x] > w[y];
}
int find(int x)
{
return fa[x] == x ? x : fa[x] = find(fa[x]);
}
void change(int now, int v, int val) //差分维护贡献,每个连通块出现时 + w_i,消失时 - w_i
{
sum[sz[now]] -= val;
sz[now] += v;
sum[sz[now]] += val;
return;
}
void merge(int now, int fat, int val)
{
int f = find(fat);
fa[now] = f;
sum[sz[now]] -= val; //删除被合并连通块的贡献
change(f, sz[now], val);
return;
}
void del(int now)
{
if (fat[now] && vis[fat[now]]) //当父亲已经在时,儿子会作为一个叶子被计算在父亲中,需减去贡献
{
int f = find(fat[now]);
change(f, -1, w[now]);
}
for (int to : e[now]) //儿子存在就和儿子合并,否则将儿子视作叶子
if (vis[to])
merge(to, now, w[now]);
else
change(now, 1, w[now]);
if (vis[fat[now]])
merge(now, fat[now], w[now]);
vis[now] = 1;
return;
}
void init(std::vector<int> P, std::vector<int> W)
{
n = P.size();
for (int i = 1; i < n; i++)
e[P[i] + 1].push_back(i + 1), fat[i + 1] = P[i] + 1;
for (int i = 0; i < n; i++)
w[i + 1] = W[i];
for (int i = 1; i <= n; i++)
if (e[i].size() == 0) //给叶子加个点,便于统一操作
{
sumleaf += w[i];
e[i].push_back(0);
}
//sz 表示每个连通块叶子个数
for (int i = 1; i <= n; i++)
id[i] = i, fa[i] = i, sz[i] = 0, vis[i] = 0;
sort(id + 1, id + n + 1, cmp);
for (int i = 1; i <= n; i++)
del(id[i]);
for (int i = 1; i <= n; i++)
sum2[i] = sum2[i - 1] + sum[i] * i, sum[i] += sum[i - 1];
return;
}
long long query(int L, int R)
{
int k = min((R - 1) / L + 1, n + 1);
return (sum2[n] - sum2[k - 1]) * L - R * (sum[n] - sum[k - 1]) + sumleaf * L;
}