CF1095F——两种贪心解法的内在一致性;kruskal

官方题解:https://codeforces.com/blog/entry/64130

题意

有两种类型的边。一种是完全图上的边,点x和y的边权是a[x]+a[y]。另一种是特殊边,由输入给出。取这两种类型的边n-1条,构成一棵MST,求MST边权和。n是经典2e5。

思路

你翻csdn之类的网站的题解,只有一个贪心结论,都不证明一下,真的太逊了。事实上,官方题解给出的“更为复杂的解法”,才是贪心结论的来源。

贪心结论

把m条边和这n-1条边:(x,y),x是某个满足a[x]最小的x,y不等于x,放到一起,跑kruskal。值得注意的是这个贪心结论不是trivial的,是需要推倒的。

关注萌新hans774882968看更多干货QAQ

更为复杂的解法

假设现在已经有若干连通分量,且已知每个连通分量的最小点权a[u[i]]。那么现在的候选边有两条,一条是边权最小的特殊边e1(特殊边已经升序排序),另一条是点权最小和次小的那两个连通分量里的点u[x][i]u[y][j]所连的边e2,边权a[u[x][i]]+a[u[y][j]]。e1的两个点可能是在同一个连通分量,此时丢弃即可。而e2必然是不同的连通分量,总是合法的。在e1和e2都合法时,就要选择边权较优秀的那条边。这种情况下,连通分量个数必定减少1。

  • 我们用并查集维护连通分量,siz数组来表示连通分量的最小点权。
  • e2要快速找到两个点权以及对应的点(作为连通分量的代表元),可以用set。set元素既需要记录最小点权,也要记录代表元对应的点。set元素需要和连通分量的代表元一一对应,所以每次并查集合并操作,都要顺带维护这个set。

这个做法为什么可行?我们发现它其实就是用了一个隐含的双指针,对两个有序数组进行二路归并,并在二路归并的同时跑kruskal。

二者联系

我们在这个set中可以发现一个性质:因为某个点权最小的点x,它所在的连通分量只会变化不会消失,所以set的第一个元素,选择x总是合法的(尽管set不一定总是取出这个连通分量,但可以(但没必要)假定一个序关系(如:点编号小的优先),使得x总是被取出)。这意味着,n-1次合并的非特殊边,事实上可以(不是必定)只来源于x和所有的y,y≠x。由此我们就得到了以上贪心结论的做法。

代码

贪心结论

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define rep(i,a,b) for(int i = (a);i <= (b);++i)
#define re_(i,a,b) for(int i = (a);i < (b);++i)
#define dwn(i,a,b) for(int i = (a);i >= (b);--i)

const int SZ = 2e5 + 5;

int n,m;LL a[SZ];int fa[SZ];

struct Edg{
    int x,y;LL w;
}e[SZ*2];

bool cmp(Edg x,Edg y){
    return x.w < y.w;
}

int find(int x){return x == fa[x] ? x : fa[x] = find(fa[x]);}

void dbg(){puts("");}
template<typename T, typename... R>void dbg(const T &f, const R &... r) {
    cout << f << " ";
    dbg(r...);
}
template<typename Type>inline void read(Type &xx){
    Type f = 1;char ch;xx = 0;
    for(ch = getchar();ch < '0' || ch > '9';ch = getchar()) if(ch == '-') f = -1;
    for(;ch >= '0' && ch <= '9';ch = getchar()) xx = xx * 10 + ch - '0';
    xx *= f;
}

int main(int argc, char** argv) {
    read(n);read(m);
    rep(i,1,n) read(a[i]);
    re_(i,0,m){
        read(e[i].x);read(e[i].y);read(e[i].w);
    }
    int idx = min_element(a+1,a+n+1)-a;
    rep(i,1,n){
        if(i == idx) continue;
        e[m++] = {i,idx,a[idx]+a[i]};
    }
    sort(e,e+m,cmp);
    rep(i,1,n) fa[i] = i;
    LL ans = 0;
    for(int u = 0,i = 0;i < m;++i){
        int fx = find(e[i].x),fy = find(e[i].y);
        if(fx != fy){
            fa[fx] = fy;
            ans += e[i].w;
            if((++u) >= n-1) break;
        }
    }
    printf("%lld\n",ans);
    return 0;
}

更为复杂的解法

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define rep(i,a,b) for(int i = (a);i <= (b);++i)
#define re_(i,a,b) for(int i = (a);i < (b);++i)
#define dwn(i,a,b) for(int i = (a);i >= (b);--i)

const int SZ = 2e5 + 5;

int n,m;LL a[SZ];int fa[SZ];LL siz[SZ];

struct X{
    int u;LL v;
};
struct Xcmp{
    bool operator () (const X &a,const X &b) const{
        if(a.v ^ b.v) return a.v < b.v;
        return a.u < b.u;
    }
};

struct Edg{
    int x,y;LL w;
}e[SZ*2];

bool cmp(Edg x,Edg y){
    return x.w < y.w;
}

void dbg(){puts("");}
template<typename T, typename... R>void dbg(const T &f, const R &... r) {
    cout << f << " ";
    dbg(r...);
}
template<typename Type>inline void read(Type &xx){
    Type f = 1;char ch;xx = 0;
    for(ch = getchar();ch < '0' || ch > '9';ch = getchar()) if(ch == '-') f = -1;
    for(;ch >= '0' && ch <= '9';ch = getchar()) xx = xx * 10 + ch - '0';
    xx *= f;
}

int find(int x){return x == fa[x] ? x : fa[x] = find(fa[x]);}

int main(int argc, char** argv) {
    read(n);read(m);
    rep(i,1,n) read(a[i]);
    re_(i,0,m){
        read(e[i].x);read(e[i].y);read(e[i].w);
    }
    sort(e,e+m,cmp);
    rep(i,1,n) fa[i] = i,siz[i] = a[i];
    multiset<X,Xcmp> st;
    rep(i,1,n) st.insert({i,siz[i]});
    LL ans = 0;
    for(int idx = 0;st.size() > 1;){
        if(idx < m && find(e[idx].x) == find(e[idx].y)){
            ++idx;continue;
        }
        X u = *st.begin();st.erase(st.begin());
        X v = *st.begin();st.erase(st.begin());
        if(idx >= m || e[idx].w > u.v+v.v){
            int fu = find(u.u),fv = find(v.u);
            fa[fu] = fv;
            ans += siz[fu] + siz[fv];
            siz[fv] = min(siz[fv],siz[fu]);
            st.insert({fv,siz[fv]});
        }
        else{
            int fx = find(e[idx].x),fy = find(e[idx].y);
            st.insert(u);
            st.insert(v);
            st.erase(st.find({fx,siz[fx]}));
            st.erase(st.find({fy,siz[fy]}));
            fa[fx] = fy;
            ans += e[idx].w;
            siz[fy] = min(siz[fy],siz[fx]);
            st.insert({fy,siz[fy]});
            ++idx;
        }
    }
    printf("%lld\n",ans);
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、MATLAB、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、MATLAB、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、MATLAB、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、MATLAB、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、MATLAB、python、web、C#、EDA、proteus、RTOS等项目的源码。 【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。【项目资源
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值