题解:AT_abc359_f [ABC359F] Tree Degree Optimization
双倍经验:P7840。
Idea
定义一个长度为 的序列叫 prufer 序列。
这个序列的求法是:
- 选择一个树上面的编号最小的叶子节点删除。
- 将这个节点所连的边的另一端的点加入到序列中。
显而易见的有一个结论:一个点的度数是它的编号在序列中的出现次数加 。
为什么?因为这个点在序列中出现一次,就说明它和一个叶子节点有连边。
如果它最后被删除了,它当时一定只连一条边。这一条边不会让它加入序列,需要额外计算。
显而易见删完之后只会存在 个点和 条边。如果它最后被留下了,它一定是这条边两个端点之一。
上述结论现在已证毕。
通过这个性质,每个点至少连一条边,所以答案初始值必须是 。
接下来我们枚举给每个点的度数加 。因为每加 就需要消耗序列的一个位置,所以最多加 次。
为点 的度数,如果度数加 ,则答案会加 。
化简一下可以得到 。设这个值为 。
可以看到当 取正整数时,它是一个公差为 的等差数列。初始当 时,它的值为 。
用一个优先队列维护 的值,每次取出这个值,都要把 加进去。即把度数再加 产生的答案重新加入。
这是一个贪心。下面给出正确性证明:
如果我们选择了一个比较劣的答案,它所能到达的状态只会更劣。
即如果 劣,则 只会更劣。
所以这道题就解完了。
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
priority_queue<pair<int,int> >q;
int n;
int a[300005];
int ans;
signed main(){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++)ans+=a[i];
for(int i=1;i<=n;i++)q.push({-3*a[i],i});
for(int i=1;i<=n-2;i++){
pair<int,int >t=q.top();q.pop();
ans-=t.first;
q.push({(t.first-2*a[t.second]),t.second});
}
cout<<ans;
return 0;
}
Tips
- 本题答案可能爆
int。 - 优先队列默认是大根堆,但我们要求最小的。可以写小根堆,当然也可以把新增的答案当成负数算进去。
- 要维护 的值,否则我们新算贡献可能不很好算。所以优先队列要用 pair 存储。
- pair 是自带排序的,先比较第一个再比较第二个,所以不用重载运算符。

浙公网安备 33010602011771号