题解:P7840 「C.E.L.U-03」重构

双倍经验:ABC359F。

Idea

定义一个长度为 n2n-2 的序列叫 prufer 序列。

这个序列的求法是:

  • 选择一个树上面的编号最小的叶子节点删除。
  • 将这个节点所连的边的另一端的点加入到序列中。

显而易见的有一个结论:一个点的度数是它的编号在序列中的出现次数加 11

为什么?因为这个点在序列中出现一次,就说明它和一个叶子节点有连边。

如果它最后被删除了,它当时一定只连一条边。这一条边不会让它加入序列,需要额外计算。

显而易见删完之后只会存在 22 个点和 11 条边。如果它最后被留下了,它一定是这条边两个端点之一。

上述结论现在已证毕。

通过这个性质,每个点至少连一条边,所以答案初始值必须是 ai\sum a_i

接下来我们枚举给每个点的度数加 11。因为每加 11 就需要消耗序列的一个位置,所以最多加 n2n-2 次。

did_i 为点 ii 的度数,如果度数加 11,则答案会加 (di+1)2di2(d_i+1)^2-d_i^2

化简一下可以得到 2×di+12\times d_i+1。设这个值为 ww

可以看到当 did_i 取正整数时,它是一个公差为 22 的等差数列。初始当 di=1d_i=1 时,它的值为 33

用一个优先队列维护 w×aiw\times a_i 的值,每次取出这个值,都要把 (w+2)×ai(w+2)\times a_i 加进去。即把度数再加 11 产生的答案重新加入。

这是一个贪心。下面给出正确性证明:

如果我们选择了一个比较劣的答案,它所能到达的状态只会更劣。

即如果 w×ajw\times a_j 劣,则 (w+2)×aj(w+2)\times a_j 只会更劣。

所以这道题就解完了。

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
  • 优先队列默认是大根堆,但我们要求最小的。可以写小根堆,当然也可以把新增的答案当成负数算进去。
  • 要维护 ii 的值,否则我们新算贡献可能不很好算。所以优先队列要用 pair 存储。
  • pair 是自带排序的,先比较第一个再比较第二个,所以不用重载运算符。
posted @   Weslie_qwq  阅读(3)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示