b_lq_城市建设 & 公路修建水题 & 新的开始(虚拟结点+MST)

城市建设

有n个城市,城市之间可铺设道路(代价为负则为收费道路,可赚钱),也可在两个城市之间建设码头实现互相可达;
给定道路的建设费用,和在每个城市建设码头的代价,求使n个城市连通的最小花费

思路
对于两个未连接的点,连一条代价小的边;
对于两个已连接的点,如果边权为负,即使它们已经有道路连通,还应多连(因为连了会赚钱)

对于码头来说:因为给出的码头都是孤立的,为了方便使用MST算法,我将各个码头与一个虚拟结点0连接起来,这样在两个城市之间建设码头的求法就和建设道路的求法一致了

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5, M=1e6+5, inf=0x3f3f3f3f;
int fa[N];
struct node {
    int u,v,w;
}e[M];
int find(int u) {
    return fa[u]==u ? u : fa[u]=find(fa[u]);
}
int kruskal(int n, int m) {
    for (int i=1; i<=n; i++) fa[i]=i;
    sort(e+1,e+m+1,[&](node a, node b){return a.w<b.w;});
    int c=n, ans=0;
    for (int i=1; i<=m; i++) {
        int fu=find(e[i].u), fv=find(e[i].v);
        if (fu!=fv) {
            fa[fu]=fv;
            ans+=e[i].w, c--;
        } else if (e[i].w<0) {
            ans+=e[i].w;
        }
    }
    return c==1 ? ans : inf;
}
int main() {
    std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    int n,m; cin>>n>>m;
    for (int i=1; i<=m; i++) cin>>e[i].u>>e[i].v>>e[i].w;
    int cost1=kruskal(n,m);

    int c=m+1, w;
    for (int i=1; i<=n; i++) {
        cin>>w; if (w==-1) continue;
        e[c].u=i, e[c].v=n+1, e[c++].w=w;
    }
    int cost2=kruskal(n+1,c-1);
    cout<<min(cost1, cost2);
    return 0;
}

公路修建问题

有n各景点,要修n-1条公路,在这n-1条公路之中,至少有k条(0≤k≤n-1)一级公路。
在满足上述条件的情况下,希望花费最多的一条公路的花费尽可能的少。
一级公路上的车速比二级公路上快,但是修路的花费要大一些。

二分边权,然后先修满k条(好像k+1,k+2...也行的)一级公路,然后修二级公路

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,k,m,fa[N];
struct edge {
    int u,v,c1,c2;
}e[2*N];
int find(int u) {return fa[u]==u ? u : fa[u]=find(fa[u]); }
bool merge(int u, int v) { //合并是否成功
    int fu=find(u), fv=find(v);
    if (fu==fv) return false;
    fa[fu]=fv;  return true;
}
bool kruskal(int w) {
    int c=0;
    for (int i=1; i<=n; i++) fa[i]=i;
    for (int i=0; i<m-1; i++) if (e[i].c1<=w && merge(e[i].u,e[i].v)) c++;
    if (c<k) return false;
    for (int i=0; i<m-1; i++) if (e[i].c2<=w && merge(e[i].u,e[i].v)) c++;
    return c==n-1;
}
int main() {
    std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    cin>>n>>k>>m; for (int i=0; i<m-1; i++) cin>>e[i].u>>e[i].v>>e[i].c1>>e[i].c2;
    int l=1, r=3e5+5;
    while (l<r) {
        int mid=l+r>>1;
        if (kruskal(mid)) r=mid;
        else              l=mid+1;
    }
    //第二个子问题和 kruskal 毫无相关,只是选公路而已,所以是道水题
    for (int i=1; i<=n; i++) fa[i]=i;
    printf("%d\n",l);
    for (int i=0; i<m-1; i++) if (e[i].c1<=l && merge(e[i].u, e[i].v)) printf("%d %d\n",i+1, 1);
    for (int i=0; i<m-1; i++) if (e[i].c2<=l && merge(e[i].u, e[i].v)) printf("%d %d\n",i+1, 2);
    return 0;
}

新的开始

有n口井,为了保证井之间电力的供应,小FF想到了两种办法:

  • 在这一口矿井上建立一个发电站,费用为=(发电站的输出功率可以供给任意多个矿井)。
  • 将这口矿井与另外的已经有电力供应的矿井之间建立电网,费用为 。
    小FF希望身为「NewBe_One」计划首席工程师的你帮他想出一个保证所有矿井电力供应的最小花费

思路:将所有操作统一为第二种操作(即建立电网),所以建立一个超级源点0,0到各井的花费为原本在每个井建立电站的费用,其它不变

#include<bits/stdc++.h>
using namespace std;
const int N=305,inf=0x3f3f3f3f;
int n,vis[N],d[N],g[N][N];
int prim() {
    fill(d,d+N,inf), memset(vis,false,sizeof vis),d[0]=0;;
    int ans=0;
    for (int i=0; i<=n; i++) {
        int id=-1, minD=inf;
        for (int j=0; j<=n; j++) if (!vis[j] && d[j]<minD) {
            id=j, minD=d[j];
        }
        vis[id]=1, d[id]=minD, ans+=minD;
        for (int j=0; j<=n; j++) if (!vis[j]) {
            d[j]=min(d[j],g[id][j]);
        }
    }
    return ans;
}
int main() {
    std::ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    cin>>n; for (int i=1; i<=n; i++) cin>>g[0][i], g[i][0]=g[0][i];
    for (int i=1; i<=n; i++)
    for (int j=1; j<=n; j++)
        cin>>g[i][j];
    cout<<prim();
    return 0;
}
posted @ 2020-10-11 09:07  童年の波鞋  阅读(87)  评论(0编辑  收藏  举报