题解/算法 {F - Tree Degree Optimization}
@LINK: https://atcoder.jp/contests/abc359/tasks/abc359_f
;
根据@LINK: (https://editor.csdn.net/md/?not_checkout=1&articleId=128695608)-(@LOC_1)
, 对于一个数组Di
(表示每个点的度数) 只要满足Di>=1 && Di之和==(N-1)*2
那么这个数组 他一定可以形成一棵树;
因此现在问题转化为: 你需要构造一个数组Di
(满足上面的条件 即Di>=1 && Di之和==(N-1)*2
), 使得Di*Di*Ai之和
最小;
假如说 他求的值是Di * Ai
, 那么这个问题 可以转换为: 有N个物品 每个价值是Ai
(每种物品有无限个), 然后你需要拿(N-1)*2 - N = N-2
个 (因为Di>=1
即每个物品初始就有一个), 此时就简单的多 如果要求最大值 就再拿N-2
个最大价值, 求最小值 就拿N-2
个最小价值;
但现在求的价值是Di*Di * Ai
, 此时就不可以按照上面的做法了 因为比如Di=3
那么他并不是说拿了3个物品 而是拿了3*3 = 9
个物品, 假如他是求的最大值 因为[1,4,9,16,25,36...]
的差分数组是[3,5,7,9,11,...]
是单独递增的 因此我们让最大价值的物品 他的Di
最大 让其他物品的Di=1
就可以让答案是最大值;
可是, 现在求的是最小值…
我们先猜测一种做法:
我们令Sol( sum)
表示 满足Di>=1 && Di之和等于sum
时的最小值方案对应的Di
数组; (我们答案要求的是Sol( (N-1)*2)
);
假如此时给定你Sol(sum)
这个方案 然后让你选择一个Di
执行Di+=1
, 比如此时Di = [a,b,c]
然后你要求[a+1,b,c] / [a,b+1,c] / [a,b,c+1]
这三个方案的最小值, 他的答案 因为(Di+1)*(Di+1)*Ai - Di*Di*Ai = (2Di+1)*Ai
他表示增量 我们要找一个增量的最小值, 因此你可以遍历所有的元素 找到最小的(2Di+1)*Ai
让他Di+=1
即可;
那么, 我们要得到Sol( x)
他一定可以通过递归的方式 即Sol(x-1) -> Sol(x-2) -> ...
通过每一次+=1
来构造出最终答案么?
这个 你需要严格的证明, 因为Sol(x)
不一定非得是从Sol(x-1)
构造来的, 即比如说 Sol(x-1) = [1,1,3]
那么他+1
后 能得到的只有[2,1,3] / [1,2,3] / [1,1,4]
然而 这些不一定是Sol(x)
因为Sol(x) 可能是 [1,3,2]
即Sol(x)
是从[1,2,2]
来的 而[1,2,2]
并不是Sol(x-1)
的最优解; 换句话说 x
的最优解 不一定是从x-1
的最优解来的;
虽然说 这个猜测是正确的… 即x
的最优解 一定是可以从x-1
的最优解过来的, 但是 你需要证明;
我们让Ai
数组 递增, 那么可以 答案的Di
数组 一定是递减的; (证明: 比如Ai < Aj
然而Di < Dj
那么此时Di*Di*Ai + Dj*Dj*Aj > (Di+1)*(Di+1)*Ai + (Dj-1)*(Dj-1)*Aj
因为(2Dj+1)*Aj > (2Di+1)*Ai
😉 (不一定是严格递减 比如Ai=9 < Aj=10
那么Di=2,Dj=2
是最优的 小于Di=3,Dj=1
);
举个例子 比如Ai = [1,2,3,4,5]
那么Di
数组可以是[4,1,1,1,1]; [3,2,1,1,1]; [2,2,2,1,1]
;
由于Ai
有重复元素 这就比较复杂了, 对于Ai
相同的Di
值 他们的差值 最大是1
;
严格证明我还不会…
感性的证明下, 比如Ai = [a,a,b,b,c]
, 最初Di = [1,1,1,1,1]
然后假如你还需要往Di
里面 加上s
, 因为a<b
所以肯定是优先让a
的Di
值增加, 即Di = [2,2,1,1,1]
然后下一步对于(Di=2,a) 和 (Di=1,b)
他俩 让谁的Di += 1
呢? 这需要比较 即根据上面讲的增量计算公式 比较下(2Da+1)*a 和 (2Db+1)*b
选择他俩的最小值;
这个过程 是种构造的过程, 看似 好像他和上面的(x
最优解来自于x-1
的最优解)是一样的, 但是 其实是不一样的, 因为 我们这里讲的是构造的过程, 即我们是在构造出这个方案, 其实 并不是 每次的加一 (虽然算法确实是每次在+1
), 即Ai = [a...a, b...b, c...c]
最初Di = [1...1]
, 第一次 我们比较(a,Da) (b,Db) (c,Dc) ...
然后选择最小增量后 是直接让a...a
他们 全部的Di
都进行+=1
操作, 即每次找到最小增量后 是让这些Ai
相同元素 都进行Di += 1
操作; 当然其实本质上 是等价于一个个元素每次进行Di += 1
操作, 你还是要证明 为什么sum
可以拆分成若干个s1 + s2 + s3...
;
int N; cin>> N;
vector<int> A(N); for( auto & i : A){ cin>> i;}
using Item_ = pair<Int64_, pair<int,int>>;
priority_queue< Item_, vector<Item_>, std::greater<> > Heap;
auto NexCost = []( int _a, int _id){
return _a * 1LL * ((_id+1)*1LL*(_id+1) - _id*1LL*_id);
};
for( auto a : A){
Heap.push( {NexCost(a,1), {a, 1}});
}
FOR_( _, 1, N-2){
auto top = Heap.top(); Heap.pop();
//> 注意 下面计算NexCost里是`top.second+1` 不要忘记`+1`, 因为你计算的是`NextCost` 即新的增量, 你此时`Di`值已经`+=1`了 所以计算`nextCost`是`Di+1`的值;
auto v = NexCost( top.second.first, top.second.second + 1);
Heap.push( {v, {top.second.first, top.second.second + 1}});
}
Int64_ ANS = 0;
while( !Heap.empty()){
auto top = Heap.top(); Heap.pop();
ANS += (top.second.first * 1LL * top.second.second * top.second.second);
}
cout<< ANS<< "\n";