题解/算法 {F - Tree Degree Optimization}

题解/算法 {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 所以肯定是优先让aDi值增加, 即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";
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值