POJ2054与2018年CCPC桂林A题(集合平均和最大的贪心)

本文详细解析了POJ2054题目的算法思路,通过将问题转化为树的染色问题,利用带权并查集和优先队列实现了O(nlogn)的时间复杂度解决方案。同时,对比分析了2018年CCPC桂林A题,展示了两题之间的联系。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

POJ2054的题意:

有一棵n个节点的树,树上每个节点的权值是a[i],要给这棵树涂色,规定涂某个节点前,它的所有父节点都要被涂色,给每个节点涂色花费一单位时间。时间从1开始,假设涂某个点的时间是t[i],总代价是 ∑i=0na[i]∗t[i]\sum_{i=0}^n a[i]*t[i]i=0na[i]t[i],求最小总代价。(n≤1000n\le1000n1000

2018年CCPC桂林A题的题意:

给两个序列A[i],B[i],把它们归并到序列C[i],并要保证在序列C里面A的顺序不变,B的顺序也不变,代价是∑i=0nc[i]∗i\sum_{i=0}^n c[i]*ii=0nc[i]in≤200000n\le200000n200000
那我们把A和B当做两条链并加上根节点就变成POJ2054了。

题解:

如果没有先涂祖先的限制,那么肯定是从大到小涂。有了这个限制后,也应该在涂完父节点后马上涂权值大的节点,这样才能做得和没有限制时更接近。
证明:
父节点是p,大儿子是q,p还有k个小儿子。
先p再q再小儿子们:① Cp∗T+Cq∗(T+1)+∑i=1kCsoni∗(T+1+i)Cp*T+Cq*(T+1)+\sum_{i=1}^k Csoni*(T+1+i)CpT+Cq(T+1)+i=1kCsoni(T+1+i)
先p再小儿子们再q:② Cp∗T+∑i=1kCsoni∗(T+i)+Cq∗(T+2)Cp*T+\sum_{i=1}^k Csoni*(T+i)+Cq*(T+2)CpT+i=1kCsoni(T+i)+Cq(T+2)
①-②得∑i=1kCsoni∗i−Cq∗k\sum_{i=1}^k Csoni*i-Cq*ki=1kCsoniiCqk
Cq≥Csoni,k∗Cq≥∑i=1kCsoni∗iCq\ge Csoni,k*Cq\ge \sum_{i=1}^k Csoni*iCqCsoni,kCqi=1kCsonii
得证

现在我们知道了涂了某个父节点之后要马上涂最大的儿子,可以把它们合并成一个集合,那么有多个集合的时候应该先选哪个呢?
假设集合1:父节点是p,子节点是q
集合2:有k个节点
先1后2:①Cp∗(T+1)+Cq∗(T+2)+∑i=1kCi∗(T+2+i)Cp*(T+1)+Cq*(T+2)+\sum_{i=1}^k Ci*(T+2+i)Cp(T+1)+Cq(T+2)+i=1kCi(T+2+i)
先2后1:②Cp∗(T+k+1)+Cq∗(T+k+2)+∑i=1kCi∗(T+i)Cp*(T+k+1)+Cq*(T+k+2)+\sum_{i=1}^k Ci*(T+i)Cp(T+k+1)+Cq(T+k+2)+i=1kCi(T+i)
①-②得−(Cp+Cq)∗k+∑i=1kCi∗2-(Cp+Cq)*k+\sum_{i=1}^k Ci*2(Cp+Cq)k+i=1kCi2
(Cp+Cq)/2≥(∑i=1kCi)/k(Cp+Cq)/2\ge (\sum_{i=1}^k Ci)/k(Cp+Cq)/2(i=1kCi)/k时先1后2
也就是说集合的和的平均值越大的越先。

用优先队列实现带权并查集的合并就可以了。时间复杂度O(nlogn)O(nlogn)O(nlogn)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<queue>
#define rep(i,l,r) for (int i=l;i<=r;i++)
#define per(i,l,r) for (int i=l;i>=r;i--)
using namespace std;
const int maxn=200500;
struct node{
    double avg; int sum,sz,rt,id;
    friend bool operator <(node a,node b){
        return a.avg<b.avg;
    }
}f[maxn];
priority_queue<node> q;
vector<int> g[maxn];
int fa[maxn],a[maxn];
int n,rt,ans,idx;
void dfs(int u){
    for (int j=0;j<(int)g[u].size();j++){
        int v=g[u][j];
        if (v!=fa[u]) fa[v]=u,dfs(v);
    }
}
int find(int x){
    if (f[x].rt==x) return x;
    return f[x].rt=find(f[x].rt);
}
int main() {
    while (1){
        scanf("%d%d",&n,&rt);
        if (!n&&!rt) break;
        rep(i,1,n) scanf("%d",&a[i]);
        rep(i,1,n) g[i].clear(),fa[i]=0; idx=0; ans=0;
        rep(i,1,n) f[i]=(node){1.0*a[i],a[i],1,i,++idx};
        rep(i,1,n-1) {
            int x,y; scanf("%d%d",&x,&y);
            g[x].push_back(y); g[y].push_back(x);
        }
        dfs(rt);
        rep(i,1,n) q.push(f[i]);
        while (!q.empty()){
            node u=q.top(); q.pop();
            if (u.id!=f[u.rt].id) continue;
            if (!fa[u.rt]) continue;
            ans+=f[find(fa[u.rt])].sz*u.sum;
            int x=find(fa[u.rt]);
            f[u.rt].rt=x;
            f[x].sum+=u.sum,f[x].sz+=u.sz;
            f[x].avg=1.0*f[x].sum/f[x].sz,f[x].id=++idx;
            q.push(f[x]);
        }
        rep(i,1,n) ans+=a[i];
        printf("%d\n",ans);
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值