刷题记录(NC17511 公交线路(求最短路),NC17509 挖沟(求最小生成树))

链式前向星(存边):

void addedge(int x, int y, int l)
{
    edge[++cnt].l = l;
    edge[cnt].t = y;
    edge[cnt].next = head[x];
    head[x] = cnt;
}

NC17511 公交线路(求最短路)

题目链接

迪杰斯特拉算法:

思想:

1、dijkstra算法是求一个点到其他所有点的最短距离,以下设该点为s,即求s点到其他所有点的最短距离

2、用一个dis数组来记s到所有节点到的最短距离,首先先将该距离初始化为最大值,对于dis[s] = 0,到自己本身的距离就为0。首先先将所有与s有连边先更新dis值

3、用一个vis数组来标记当前点是否有访问过,每次从所有节点中找dis值最小且未访问过的节点x,将其当作中间节点,标记为访问过,即该点到s的最小距离已经求出,然后利用该最小距离作为中间节点,更新每个节点的dis值。即计算dis[x] + ve[x][k] < dis[k](k为任意一个节点)出现,就更新

4、优化:可以利用优先队列即最小堆来减少复杂度,因此每次都要从所有节点中找出dis值最小且未访问过的节点,那么我们可以将所有值的dis值存入优先队列中,每次取出dis值最小的(即队首元素),判断是否访问过,访问过则取下一个。这样直接取元素会更快

5、如果求s-k的最短距离:dis[k]

完整代码:

# include <bits/stdc++.h>
using namespace std;
int n, m, s, t;
struct ty1{
    int t, l, next;
}edge[20010];
struct ty2{
    int x, dis;
    bool operator < (const ty2 &a)const{
        return dis>a.dis;
    }
};
priority_queue<ty2> q;
int head[1010], dis[1010];
bool vis[1010];
int cnt = 100;
void addEdge(int x, int y, int l)
{
    edge[++cnt].l = l;
    edge[cnt].t = y;
    edge[cnt].next = head[x];
    head[x] = cnt;
}
int di(int s, int t)
{
    memset(dis, 0x3f, sizeof(dis));
    memset(vis, 0, sizeof(vis));
    dis[s] = 0;
    ty2 tmp;
    tmp.dis = 0, tmp.x = s;
    q.push(tmp);
    
    while (!q.empty())
    {
        ty2 tmp = q.top();
        q.pop();
//         cout<<tmp.dis<<endl;
        if (vis[tmp.x])
            continue;
        vis[tmp.x] = 1;
        for (int i=head[tmp.x]; i!=-1; i=edge[i].next)
        {
            int y = edge[i].t;
            if (vis[y]) continue;
            if (edge[i].l+dis[tmp.x]<dis[y])
            {
                dis[y] = edge[i].l+dis[tmp.x];
                ty2 tmp2;
                tmp2.x = y, tmp2.dis = dis[y];
                q.push(tmp2);
            }
        }
    }
    if (dis[t]>=0x3f3f3f3f)
        return -1;
    return dis[t];
}
int main()
{
    cin>>n>>m>>s>>t;
    memset(head, -1, sizeof(head));
    
    for (int i=1; i<=m; i++)
    {
        int x, y, l;
        cin>>x>>y>>l;
        addEdge(x, y, l);
        addEdge(y, x, l);
    }
    cout<<di(s, t)<<endl;
    
    
    return 0;
}

Bellman-Ford算法

思想:

1、与迪杰斯特拉算法相似,求一点到所有其他点的最小距离,设该起点为s,dis数组表示s到所有其他点的最小距离,同样的先初始化为最大值,且dis[s] = 0,。

2、枚举每个节点i,再枚举每个中间节点j,一旦中间节点可以使其更新dis值就更新,即

dis[j] + ve[j][i] < dis[i] (即s到j的最小距离+j到i的距离<s到i的最小距离)就更新dis值

3、如何判断所有节点均已算出与s的最短距离,即一旦所有节点的距离都无法更新时,则说明s到其他所有节点的最小距离均已算出,无法再进行更新。

4、优化:用队列,我们每次没必要都将所有点的边都看一遍,用一个队列,里面存放所有要进行迭代的点(作为中间节点),首先是放着初始点s,然后将s能更新的所有的点都放入队列中(不能重复放入节点),这样更新直到队列为空为止

5、如何判断是否为重复入队的节点,用一个vis数组,入队了就将其设为1,出队就设为0,这样就可以避免重复

6、答案s-k的最短距离即为dis[k]

完整答案:

# include <bits/stdc++.h>
using namespace std;
struct ty1{
    int t, l, next;
}edge[20010];
queue<int>q;
int cnt, n, m, s, t;
int head[10010], dis[1010];
bool vis[10100];
void addedge(int x, int y, int l)
{
    edge[++cnt].l = l;
    edge[cnt].t = y;
    edge[cnt].next = head[x];
    head[x] = cnt;
}
int spfa(int s, int t)
{
    memset(vis, 0, sizeof(vis));
    memset(dis, 0x3f, sizeof(dis));
    vis[s] = 1;
    dis[s] = 0;
    q.push(s);
    while (!q.empty())
    {
        int x = q.front();
        q.pop();
        vis[x] = 0;
        for (int i=head[x]; i!=-1; i = edge[i].next)
        {
            int y = edge[i].t;
            if (dis[x] + edge[i].l < dis[y])
            {
                dis[y] = dis[x] + edge[i].l;
                if (!vis[y])
                {
                    q.push(y);
                    vis[y] = 1;
                }
            }
        }
    }
    if (dis[t] >= 0x3f3f3f3f)
        return -1;
    else
        return dis[t];
}
int main()
{
    memset(head, -1, sizeof(head));
    cin>>n>>m>>s>>t;
    for (int i=1; i<=m; i++)
    {
        int x, y, len;
        cin>>x>>y>>len;
        addedge(x, y, len);
        addedge(y, x, len);
    }
    cout<<spfa(s, t);
    
    
    return 0;
}

Floyd算法:

思想:

1、可以求两点间的最短距离,枚举两点,枚举中间节点

2、顺序:先枚举中间节点,再枚举两点

for (int k=1; k<=n; k++)

for (int i=1; i<=n; i++)

for (int j=1; j<=n; j++)

{
    if ((i!=j) && (j!=k) && (i!=k))
    {
        if (f[i][k] + f[k][j] <= f[i][j])
        {
            f[i][j] = f[i][k] + f[k][j]; 
        }
    }
}

NC17509 挖沟(求最小生成树)

题目链接

Prim算法:

思想:

1、从根节点开始,不断加入最小边,且加入的边仍能为树

2、用优先队列(最小堆),用一个vis数组表示该点是否已经加入树中,将所有要更新的节点放入队列。首先标记该根节点加入树,且初始队列里放根节点所有连接的边,每次从队列中取最短的边,取出该边所连接的点,更新最小距离。如果该点未加入树中,将该点所连接的边放入队列中,并标记已经加入树中,继续更新。

3、最后返回该最小距离即可

完整代码:

# include <bits/stdc++.h>
using namespace std;
struct ty1{
    int t, l, next;
}edge[500000*2+10];
struct ty2{
    int x, len;
    bool operator < (const ty2& a)const{
        return len>a.len;
    } 
};
int n, m, cnt;
int head[100000+10], vis[100000+10];
void addEdge(int a, int b, int len)
{
    edge[++cnt].l = len;
    edge[cnt].t = b;
    edge[cnt].next = head[a];
    head[a] = cnt;
}
priority_queue<ty2> q;
int prim()
{
    int ans = 0;
    vis[1] = 1;
    for (int i=head[1]; i!=-1; i=edge[i].next)
    {
        ty2 tmp;
        tmp.len = edge[i].l;
        tmp.x = edge[i].t;
        q.push(tmp);
    }
    while (!q.empty())
    {
        ty2 tmp = q.top();
        q.pop();
        int x = tmp.x;
        if (vis[x]) continue;
        vis[x] = 1;
        ans += tmp.len;
        for (int i = head[x]; i!=-1; i = edge[i].next)
        {
            int x = edge[i].t;
            if (vis[x]) continue;
            ty2 tmp;
            tmp.x = x, tmp.len = edge[i].l;
            q.push(tmp);
        }
    }
    return ans;
}
int main()
{
    cin>>n>>m;
    memset(head, -1, sizeof(head));
    for (int i=1; i<=m; i++)
    {
        int x, y, len;
        cin>>x>>y>>len;
        addEdge(x, y, len);
        addEdge(y, x, len);
    }
    cout<<prim();
    
    
    return 0;
}

4、优化:没必要将该点连接的所有结点均放入队列中,我们直接找最小边放入即可。即用一个dis数组,来记当前点所有边的最小距离,每次比较每次更新即可

完整代码:

# include <bits/stdc++.h>
using namespace std;
struct ty1{
    int t, l, next;
}edge[500000*2+10];
struct ty2{
    int x, len;
    bool operator < (const ty2& a)const{
        return len>a.len;
    } 
};
int n, m, cnt;
int head[100000+10], vis[100000+10];
void addEdge(int a, int b, int len)
{
    edge[++cnt].l = len;
    edge[cnt].t = b;
    edge[cnt].next = head[a];
    head[a] = cnt;
}
priority_queue<ty2> q;
int dis[100000+10];
int prim()
{
    int ans = 0;
    vis[1] = 1;
    memset(dis, 0x7f, sizeof(dis));
    for (int i=head[1]; i!=-1; i=edge[i].next)
    {
        ty2 tmp;
        tmp.len = edge[i].l;
        tmp.x = edge[i].t;
        dis[tmp.x] = tmp.len;
        q.push(tmp);
    }
    while (!q.empty())
    {
        ty2 tmp = q.top();
        q.pop();
        int x = tmp.x;
        if (vis[x]) continue;
        vis[x] = 1;
        ans += tmp.len;
        for (int i = head[x]; i!=-1; i = edge[i].next)
        {
            int x = edge[i].t;
            if (vis[x]) continue;
            if (dis[x]>edge[i].l)
            {
                ty2 tmp;
                dis[x] = edge[i].l;
                tmp.x = x, tmp.len = edge[i].l;
                q.push(tmp);
            }
        }
    }
    return ans;
}
int main()
{
    cin>>n>>m;
    memset(head, -1, sizeof(head));
    for (int i=1; i<=m; i++)
    {
        int x, y, len;
        cin>>x>>y>>len;
        addEdge(x, y, len);
        addEdge(y, x, len);
    }
    cout<<prim();
    
    
    return 0;
}

Kruskal算法

思想:

1、将所有的边从小到大排序,贪心的选取最小边加入树中,并且保证该边加入后不会在树中形成环

2、如何判断是否可以加入树中,利用并查集,每次加入边,先判断其两点的父亲结点是否相同,相同说明加入该边则会形成环,不相同就将两点连边

完整代码:

# include <bits/stdc++.h>
using namespace std;
int n, m;
int fa[100000+10];
struct ty{
    int x, y, z;
}t[500000+10];
bool cmp(ty t1, ty t2)
{
    return t1.z<t2.z;
}
int find(int x)
{
    return x == fa[x]? x: fa[x]=find(fa[x]);
}
int ans;
int main()
{
    cin>>n>>m;
    for (int i=1; i<=n; i++)
        fa[i] = i;
    for (int i=1; i<=m; i++)
    {
        int x, y, z;
        cin>>x>>y>>z;
        t[i].x = x;
        t[i].y = y;
        t[i].z = z;
    }
    sort(t+1, t+1+m, cmp);
    for (int i=1; i<=m; i++)
    {
        int fx = find(t[i].x), fy = find(t[i].y);
        if (fx!=fy)
        {
            ans += t[i].z;
            fa[fx] = fy;
        }
    }
    cout<<ans<<endl;
    
    
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值