NOIP2017 7.17模拟 Minimum (最短路+最小生成树)

题目描述:
给出一幅由 n 个点 m 条边构成的无向带权图。
其中有些点是黑点,另外点是白点。
现在每个白点都要与他距离最近的黑点通过最短路连接(如果有很多个,连所有的),我们想要使得花费的代价最小。请问这个最小代价是多少?
注意:最后选出的边保证每个白点到黑点的距离任然是最短距离。(这句话题解代码也无法实现,就不管了)

输入:
第一行两个整数 n,m ;
第二行 n 个整数,0 表示白点,1 表示黑点;
接下来 m 行,每行三个整数 x,y,z ,表示一条连接 x 和 y 点,权值为 z 的边。

输出:
如果无解,输出“impossible”,否则,输出最小代价。

样例数据:
输入
5 7
0 1 0 1 0
1 2 11
1 3 1
1 5 17
2 3 1
3 5 18
4 5 3
2 4 5
输出
5

【数据范围】
对 30% 的输入数据 :1≤n≤10,1≤m≤20;
对 100% 的输入数据 :1≤n≤100000,1≤m≤200000,1≤z≤1000000000 。

题意:将所有白点和黑点通过在最短路上的路径连接,求最小代价,无解的情况是存在白点与黑点无法连通。通过分析我们可以发现,有解的情况可以分为两个问题考虑:
1、求最短路;
2.并找到在最短路上的边;
3、通过这些边跑一边最小生成树,统计边权之和。

对于第一个问题,因为是要求任意一个白点到任意一个黑点的距离,我们可以想到建一个超级点,由它向每一个黑点连一条边权为0的有向边,然后由这个超级点跑一次最短路(迪杰斯特拉或spfa),就行了;

对于第二个问题,由最短路的更新方法

if(dis[a]+val[i]<dis[to[i]])
  dis[to[i]]=dis[a]+val[i];

我们可以想到,遍历每条边,满足dis[edge[i].start]==dis[edge[i].end+edge[i[.val那么这条边一定是最短路上的边,由此我们可以找到所有最短路上的边

对于第三个问题,裸的最小生成树,在此就不赘述了;

#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<ctime>
#include<cstring>
#include<string>
#include<iomanip>
#include<iostream>
#include<cctype>
#include<queue>
using namespace std;
int n,m,tot,sup,cnt,len,fa[100005],next[500010],first[100005],to[500010];
long long dis[100005];
long long val[500010];
bool black[100005],ok=true;
//---------------------
priority_queue< pair<int,int> > que;
pair<int,int> temp;
//---------------------
struct node
{
    int st,end;
    long long val;
}a[400010];

bool cmp(const node&a,const node&b)
{
    return a.val<b.val;
}
//---------------------
inline long long Readint()
{
    long long i=0,f=1;
    char ch;
    for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
    if(ch=='-') f=-1;
    for(;ch>='0'&&ch<='9';ch=getchar()) i=(i<<1)+(i<<3)+ch-'0';
    return i*f;
}
//---------------------
inline void add(int x,int y,long long z)
{
    next[++tot]=first[x];
    first[x]=tot;
    to[tot]=y;
    val[tot]=z;
}
//---------------------
inline void djstra(int root)
{
    dis[root]=0;
    temp.first=0;
    temp.second=root;
    que.push(temp);
    while(!que.empty()){
        temp=que.top();
        que.pop();
        int a=temp.second;
        for(int i=first[a];i;i=next[i]){
            if(dis[a]+val[i]<dis[to[i]]){
                dis[to[i]]=dis[a]+val[i];
                    pair<int,int> s;
                    s.first=-dis[to[i]];
                    s.second=to[i];
                    que.push(s);
            }
        }
    }
}
//---------------------
inline int find(int x)
{
    if(x==fa[x]) return x;
    fa[x]=find(fa[x]);
    return fa[x];
}
//---------------------
int main()
{
    freopen("minimum.in","r",stdin);

    n=Readint(),m=Readint();
    memset(first,0,sizeof(first));
    for(int i=0;i<=n;i++) fa[i]=i;
    for(int i=0;i<=n;i++) dis[i]=1e16+7;
    for(int i=1;i<=n;i++){
        black[i]=Readint();
        if(black[i]) add(sup,i,0);
    } 
    for(int i=1;i<=m;i++){
        int x,y;
        long long z;
        x=Readint(),y=Readint(),z=Readint();
        add(x,y,z),add(y,x,z);
    }
    djstra(sup);

    for(int i=1;i<=n;i++) if(dis[i]==1e16+7) ok=false;
    if(!ok){
        cout<<"impossible"<<endl;
        return 0;
    }
    for(int i=1;i<=n;i++){
        for(int p=first[i];p;p=next[p]){
            if(dis[to[p]]==val[p]+dis[i]){
                a[++len].st=i,a[len].end=to[p],a[len].val=val[p];
            }
        }
    }
    long long ans=0;
    sort(a+1,a+1+len,cmp);
    for(int i=1;i<=len;i++){
        int fx=find(a[i].st),fy=find(a[i].end);
        if(fx!=fy){
            fa[fx]=fy;
            ans+=a[i].val;
            cnt++;
        }
        if(cnt==n-1) break;
    }
    cout<<ans<<endl;
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
你好!感谢你的提问。根据你的要求,我将为你解答关于NOIP2017普及t3问题的内容。 在这道题目中,我们需要解决一个关于棋盘的问题。题目描述了一个n×m的棋盘,每个格子上有一个非负整数。开始时,小明站在左上角的格子上,他可以向右或向下移动一步,每次移动的代价为目标格子上的数值。我们需要找到一条从左上角到右下角的路径,使得移动的总代价最小。 解决这个问题的一个常见的方法是使用动态规划(Dynamic Programming)。我们可以创建一个二维数组dp,其中dp[i][j]表示从起点到达坐标为(i, j)的格子时的最小代价。然后,我们可以按照从左上角到右下角的顺序依次计算dp数组的值。 具体的计算方法如下: 1. 首先,我们可以初始化dp数组的第一行和第一列,即dp[0][j]和dp[i][0],它们表示从起点到达第一行和第一列的格子时的最小代价。初始化的方法是累加前面的格子的代价。 2. 接下来,我们可以使用一个双重循环,从(1, 1)开始遍历整个棋盘。对于每个格子(i, j),我们可以选择从上方格子(i-1, j)或左方格子(i, j-1)中选择一个代价较小的路径,并加上当前格子的代价。即dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j]。 3. 最后,当我们计算完dp数组的所有值后,dp[n-1][m-1]即为从起点到达右下角的格子时的最小代价。 这样,我们就可以得到从左上角到右下角的最小代价。希望能对你的问题有所帮助!如果你还有其他问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值